I'm trying to modify the sample json shown to test in swagger if a POST works or doesn't work. From where should I modify this?
That is, I would have to modify the json that is displayed when we press "Try out".
To be more precise, the json to test the post is the following:
{
"Cliente": 0,
"CantidadRegistros": 0,
"TotalesPrimerVencimiento": 0,
"TotalesSegundoVencimiento": 0,
"Detalle": [
{
"Consorcio": 0,
"UnidadFuncional": 0,
"Periodo": "string",
"Propietario": "string",
"Ubicacion": "string",
"Email": "string",
"FechaPrimerVencimiento": "2021-12-15",
"ImportePrimerVencimiento": 0,
"FechaSegundoVencimiento": "2021-12-15",
"ImporteSegundoVencimiento": 0,
"CodigoDePagoElectronico": "string",
"CodigoDeBarras": "string"
}
]
}
What I would like to modify is the format in which the date is displayed. Currently it is dd-mm-yyyy and I want to modify it to dd/mm/yyyy
Tried modifying this with the following DisplayFormat code but it didn't work for me:
[JsonPropertyName("FechaSegundoVencimiento")]
[FromQuery(Name = "FechaSegundoVencimiento")]
[ModelBinder(BinderType = typeof(DateTimeModelBinder))]
[DisplayFormat(DataFormatString = "{0:dd/MM/yyyy}", ApplyFormatInEditMode = true)]
[DataType(DataType.Date)]
public DateTime? DueDate2 { get; set; }
I hope your help! Thanks!
The supported and out of the box way to do this, is by using XML comments, generating documentation files on build and having Swashbuckle read these comments:
Model:
public class Product
{
/// <summary>
/// The name of the product
/// </summary>
/// <example>Men's basketball shoes</example>
public string Name { get; set; }
// ...
Startup:
services.AddSwaggerGen(c =>
{
c.SwaggerDoc("v1", ...);
var filePath = Path.Combine(System.AppContext.BaseDirectory, "Your.App.xml");
c.IncludeXmlComments(filePath);
}
If memory serves me right, this generates one example per model property. So if you reuse models between different APIs and want different examples, you'll need to branch out to other libraries.
I have used mattfrear/Swashbuckle.AspNetCore.Filters, where you can annotate your methods with [SwaggerRequestExample(typeof(...)] and [SwaggerResponseExample(typeof(...))], where ... can provide an example object that will be serialized to JSON, but note that in various cases they recommend to use Swashbuckle's built-in way.
However, you say:
Currently it is dd-mm-yyyy and I want to modify it to dd/mm/yyyy
Don't. There's no predefined date format for JSON, but most APIs accept ISO-8601(ish) formats by default. Don't go make that culture-specific.
Related
I am working with data annotations in my MVC 4 application to handle validation. One requirement of this was to fully localise the all error messages and the regular expressions.
In order to do this, I wrote an attribute extension, as below.
View:
#Html.LabelFor(m => m.Postcode, new { #class = "input-label", #for = "valid-postcode" })
#Html.TextBoxFor(m => m.Postcode, new { type = "text", id = "valid-postcode", autocomplete = "off" })
#Html.ValidationMessageFor(m => m.Postcode)
Model:
// Postcode
[Required(ErrorMessageResourceType = typeof(Resources.FormValidation), ErrorMessageResourceName = "requiredMsg")]
[Localised(typeof(Resources.FormValidation), "postcodeRegEx", "postcodeMsg")]
[Display(Name = "postcode", ResourceType = typeof(Resources.FormLabels))]
public string Postcode { get; set; }
Attribute Extension:
public class LocalisedAttribute : RegularExpressionAttribute
{
public LocalisedAttribute(Type resource, string regularExpression, string errorMessage)
: base(Resources.FormValidation.ResourceManager.GetString(regularExpression))
{
ErrorMessageResourceType = resource;
ErrorMessageResourceName = errorMessage;
}
}
If I set a breakpoint on my Attribute Extension and start the application, I hit the break point when I view the page that contains my form elements. As I test I added an additional field that also uses the same extension and it hit the breakpoint twice. So from this, I know it's working and I know it's getting my regular expressions from my resource files.
THE PROBLEM
I have a menu to switch the culture used in the application. When I choose a new culture and the page is refreshed, all references to my resource files used in my Views and the Display Name and Error Messages in data annotations pick up the culture change and use the correct resource file.
However, the regular expression is NOT updated and the breakpoint I set is not hit again. This means my extension is still using the regex it picked up when it was hit and therefore doesn't validate correctly.
I can post more details on how the culture is changed from this menu if needed, but the basic structure is
A controller that changes the culture and returns the user to the same page
Modified my routing to include the culture e.g. www.site.com/en-GB/View
What I need is for my attribute extension to be hit every time the culture is switched, and not just the first time the application is started.
Is this possible, or should I be revising my whole approach?
Your menu strings are referenced at runtime, while your attribute is compiled before your app runs
I can understand why this is confusing.
Resource files are fundamentally meant to be used for dynamic behavior. And string values can be altered at run time.
But, when we dig into the usage of this particular string, you are using Resources.FormValidation.ResourceManager.GetString(regularExpression) resource string as part of the compile instruction to create Postcode. The Razor Framework will use this data to create annotation templates for validation.
[Required(ErrorMessageResourceType = typeof(Resources.FormValidation), ErrorMessageResourceName = "requiredMsg")]
[Localised(typeof(Resources.FormValidation), "postcodeRegEx", "postcodeMsg")]
[Display(Name = "postcode", ResourceType = typeof(Resources.FormLabels))]
public string Postcode { get; set; }
You are using this string the postcodeRegEx string at COMPILE TIME:
In some cases, compiled and pre-compiled code dependent on string literals can behave differently if the string changes. In other cases, like validation attribute behaviors, you do not get to "re-compile" your object's behavior so easily.
Possible Solutions
To achieve this kind of "end-around", you have to go outside the standard
1) implement an extension to validation attribute (ValidationAttribute), inheriting from RegularExpressionAttribute which reads the specific RegEx string from your resource file and passes it to the base RegEx Attribute.
// New attribute loads RegEx when needed
[AttributeUsage(AttributeTargets.Field | AttributeTargets.Property, AllowMultiple = false)]
public class LocalisedAttribute : RegularExpressionAttribute
{
static LocalizedRegexAttribute()
{
// necessary to enable client side validation
DataAnnotationsModelValidatorProvider.RegisterAdapter(typeof(LocalizedRegexAttribute), typeof(RegularExpressionAttributeAdapter));
}
public LocalisedAttribute(Type resource, string regularExpressionKey, string errorMessage)
: base(LoadRegex(regularExpressionKey))
{
ErrorMessageResourceType = resource;
ErrorMessageResourceName = errorMessage;
}
private static string LoadRegex(string key)
{
var resourceManager = new ResourceManager(typeof(Resources.FormValidation));
return resourceManager.GetString(key);
}
}
2) use JQuery to make input's data-val-regex-pattern = #ViewBag.RegEx
It will reference the JQuery function
$.validator.unobtrusive.adapters.add('Postcode ', function(options) { /*...*/ });
And I suspect the data-val-regex-pattern for the Postcode input will be set to the value from your initial resource file.
I found this post about Display and EditorTemplates for MVC:
http://www.growingwiththeweb.com/2012/12/aspnet-mvc-display-and-editor-templates.html
It creates a display template to easily display a decimal formatted with currency sign.
The model used in the example:
public class TestModel
{
public decimal Money { get; set; }
}
The display template:
Views/Shared/DisplayTemplates/decimal.cshtml:
#model decimal
#{
IFormatProvider formatProvider =
new System.Globalization.CultureInfo("en-US");
<span class="currency">#Model.ToString("C", formatProvider)</span>
}
In my website I have a helper class with a method to retrieve a formatted currency string from a decimal, so I would replace the above with something like:
#model decimal
#(MyHelperClass.GetCurrencyString(Model))
And finally the view where we want to see the formatted currency:
#model TestModel
#Html.DisplayFor(e => e.Money)
Output:
<span class="currency">$3.50</span>
I can implement this without any problem. But of course i have different views where i want to view a formatted currency. But in some cases I don't want to show the currency sign.
My question now is how i should implement this small variation without to much overkill in code.
Here is my current implementation:
I've changed my display template to this:
#model decimal
#{
bool woCurrency = (bool)ViewData["woCurrency"];
}
#(MyHelperClass.GetCurrencyString(Model)Model,woCurrency))
Of course i've also changed to GetCurrencyString method to accept this additional attribute.
In my view I now have to provide this attribute too:
#Html.DisplayFor(m => m.Money, new { woCurrency = true })
So actually I everything works like it should work. But somehow I don't like this sollution that makes the view more complex.
My question to you: Is there any other method to implement something like this? Or any advice to possible optimise my current sollution?
Thanks!
You need to apply DisplayFormat attribute to your Money property. For example:
[DisplayFormat(DataFormatString = "{0:C}")]
public decimal Money { get; set; }
For more information have a look at these two links:
DisplayFormatAttribute.DataFormatString (The example at the bottom of the page uses currency formatting as an example)
ASP.NET MVC - DisplayFormat attribute
How about HtmlHelper that checks the ViewData["woCurrency"] automatically and outputs the correct result?
public static string Currency(this HtmlHelper helper, decimal data, string locale = "en-US", bool woCurrency = false)
{
var culture = new System.Globalization.CultureInfo(locale);
if (woCurrency || (helper.ViewData["woCurrency"] != null && (bool)helper.ViewData["woCurrency"]))
return data.ToString(culture);
return data.ToString("C", culture);
}
Then:
#Html.Currency(Model.Money);
So I was playing around with my ASP.NET MVC 4 solution. Every thing worked fine kept adding things but something odd started happening.
One of my Models properties was null, even though I had items in the Json passed to it.
This was the javascript object/json passed it it:
var obj = {
"plc": "False",
"al": ["386", "710"],
"pl": ["9530", "211", "783"]
};
I was using a Custom Model binder ... thought that might be the issue so I turned it off.
Tried using the JavaScriptSerializer from .NET to see it that worked:
var reader = new StreamReader(Request.InputStream);
Request.InputStream.Position = 0;
var readToEnd = reader.ReadToEnd();
var javaScript = new JavaScriptSerializer();
var searchFarmOptions = javaScript.Deserialize<Test>(readToEnd);
Got all the properties set ... WOOT.
So I tried a clean ASP.NET MVC 4 solution. To reproduce the bug.
This is from the Index.cshtml view
#{
ViewBag.Title = "title";
}
<h1>Title</h1>
Testing ...
<script src="/Scripts/jquery-1.8.2.min.js" type="text/javascript"></script>
<script>
$(function() {
var obj = {
"1pllort": "False",
"1plc": "true",
"al": ["386", "710"],
"pl": ["9530", "211", "783"]
};
var options = {
"contentType": "application/json; charset=UTF-8",
"type": "POST",
"data" : JSON.stringify(obj)
};
$.ajax("/Home/TestPost", options).done(function(data) {
console.log(data);
});
});
</script>
This is my HomeController
using System.Collections.Generic;
using System.Web.Mvc;
namespace MvcApplication3.Controllers
{
public class HomeController : Controller
{
public ActionResult Index()
{
return View("Index");
}
[HttpPost]
public ActionResult TestPost(Test model)
{
return Json(model);
}
}
public class Test
{
public List<int> PL { get; set; }
public List<int> AL { get; set; }
public bool PLC { get; set; }
public bool ALC { get; set; }
}
}
Yes, the bug is still there.
Whenever I have a property starting with "pl" as my list name is .. the "pl" list is null.
Also, it could be any name starting with "pl" ... like "plchecked"
If I rename the "plc" to "cpl" its working.
So what is going on here ... is there any naming restrictions in the model binder? What am I missing here?
Update 1
Work
PL server side now have the right values, etc. not null, but the list of numbers.
var obj = {
"pl": ["9530", "211", "783"],
"1plc": "false",
"pl-some-odd-value": "false",
"al": ["386", "710"],
"alc": "false"
};
Don't work
PL server side now have null value.
var obj = {
"pl": ["9530", "211", "783"],
"al": ["386", "710"],
"alc": "false",
"pl-odd-value": "false"
};
Work
PL has the 3 values som the json object string ...
var obj = {
"pl": ["9530", "211", "783"],
"al": ["386", "710"],
"alc": "false",
"odd-value-pl": "false"
};
Install ImranB.ModelBindingFix nuget package. I think you are hitting a bug which I have discussed in this blog post.
There is definitely a bug. I just spent hours debugging the MVC 4 source code for the same problem. The primary root cause it that you have a collection whose name is the beginning of another property name. In your example: pl and pl-odd-value.
If you play around some more, you may find that sometimes it works properly. The reason for it seeming to be random is that the model binder performs a binary search to locate the value to bind. The binary search uses a "divide and conquer" approach to finding the property in a sorted list of the properties that were posted. If the search tests the p1-odd-value in its divide and conquer, the problem occurs. If the search skips this value, no problem.
This problem is most repeatable when you simply have the pl collection with 1 element and the pl-odd-value property in the object.
Here is a bit more info related to this. There is a sortedList of all of the values posted back. In the case of your failed test, the sorted list looks like this:
alc, "false",
al[0], "386"
al[1], "710"
pl-odd-value, "false"
pl[0], "9530"
pl[1], "211"
pl[2], "783"
Notice that the sort order uses StringComparer.OrdinalIgnoreCase, which places the square bracket items AFTER the text-only item. When MVC is performing its binary search for the al or pl collection, it is searching for al or pl, NOT al[ or pl[. By searching for the collection name without a square bracket, al < alc, not greater than alc. Problem is that we want to look after alc or pl-odd-value to get to the collection.
The workaround is to make sure that none of your property names start with the name of any of your collection property names. Rename your al collection to maybe al_collection and your pl collection to maybe pl_collection.
Great answer!
I had the same problem today and I spent almost 3 hours trying to figure out what the issue was when suddenly I realized that properties with the same prefix has something weird
More info here:
https://www.google.com/webhp?sourceid=chrome-instant&ion=1&espv=2&ie=UTF-8#q=mvc+4+model+binder+bug
ASP.NET MVC 4 - ModelBinder bug
https://gist.github.com/adamchester/5606734
I've got a model...
[DataContract]
public class DeviceModel
{
[DataMember(Order=1)]
public string Alias { get; set; }
[DataMember(Order = 2)]
public string Location { get; set; }
[DataMember(Order = 3)]
public string State { get; set; }
[DataMember(Order = 4)]
public DateTime? DateCreated { get; set; }
[DataMember(Order = 5)]
public string RatePlan { get; set; }
public DeviceModel()
{
Alias = null;
Location = null;
State = null;
DateCreated = null;
RatePlan = null;
}
}
This model contains a DateTime object, as you can see. I am using this model as the data for the jqGrid plugin we are using. The only issue is, the DateCreated field shows "/Date(1285128000000)/" when the Grid loads, instead of the readable date. I've read some of the other posts here, but I don't feel that they quite fit the bill for what I'm looking for. I'm looking for a way to format this DateTime field to a human readable string? Suggestions?
JSON knows only two primitive datatypes: strings and numbers and no Date type. Data Contract Serializer supports more types. For example DateTime, DateTimeOffset, TimeSpan, Guid, Uri, XmlQualifiedName. If you send data to the client which use also Data Contract Serializer to deserialize the data you can use any from the datatypes without any problems.
The most simple solution of your problem is preparing the data on the server before sending the data. If you will serialize only objects which has only strings and numbers as properties or arrays/IList<T> then you will has no problem. For example, per default jqGrid wait the data in the ISO Date format: Y-m-d with numbers for Y, m and d. If you convert your data on the server to the Y-m-d format and use formatter:'date' in the corresponding colModel definition your problem will be solved.
You can also solve the problem on the client side using of the custom formatter and the custom unformatter.
Try Newtonsoft Json.NET as described in this post
I have an enum for one of the properties of my view-model. I want to display a drop-down list that contains all the values of the enum. I can get this to work with the following code.
What I'm wondering is whether there is a simple way to convert from an enum to an IEnumerable? I can do it manually as in the following example, but when I add a new enum value the code breaks. I imagine that I can do it via reflection as per this example, but but are there other ways to do this?
public enum Currencies
{
CAD, USD, EUR
}
public ViewModel
{
[Required]
public Currencies SelectedCurrency {get; set;}
public SelectList Currencies
{
List<Currencies> c = new List<Currencies>();
c.Add(Currencies.CAD);
c.Add(Currencies.USD);
c.Add(Currencies.EUR);
return new SelectList(c);
}
}
I'm using a helper that i found here to populate my SelectLists with a generic enum type, i did a little modification to add the selected value though, here's how it looks like :
public static SelectList ToSelectList<T>(this T enumeration, string selected)
{
var source = Enum.GetValues(typeof(T));
var items = new Dictionary<object, string>();
var displayAttributeType = typeof(DisplayAttribute);
foreach (var value in source)
{
FieldInfo field = value.GetType().GetField(value.ToString());
DisplayAttribute attrs = (DisplayAttribute)field.
GetCustomAttributes(displayAttributeType, false).FirstOrDefault()
items.Add(value, attrs != null ? attrs.GetName() : value.ToString());
}
return new SelectList(items, "Key", "Value", selected);
}
The nice thing about it is that it reads the DisplayAttribute as the title rather than the enum name. (if your enums contain spaces or you need localization then it makes your life much easier)
So you will need to add the Display attirubete to your enums like this :
public enum User_Status
{
[Display(Name = "Waiting Activation")]
Pending, // User Account Is Pending. Can Login / Can't participate
[Display(Name = "Activated" )]
Active, // User Account Is Active. Can Logon
[Display(Name = "Disabled" )]
Disabled, // User Account Is Diabled. Can't Login
}
and this is how you use them in your views.
<%: Html.DropDownList("ChangeStatus" , ListExtensions.ToSelectList(Model.statusType, user.Status))%>
Model.statusType is just an enum object of type User_Status.
That's it , no more SelectLists in your ViewModels. In my example I'm refrencing an enum in my ViewModel but you can Refrence the enum type directly in your view though. I'm just doing it to make everything clean and nice.
Hope that was helpful.
Look at Enum.GetNames(typeof(Currencies))
I am very late on this one but I just found a really cool way to do this with one line of code, if you are happy to add the Unconstrained Melody NuGet package (a nice, small library from Jon Skeet).
This solution is better because:
It ensures (with generic type constraints) that the value really is an enum value (due to Unconstrained Melody)
It avoids unnecessary boxing (due to Unconstrained Melody)
It caches all the descriptions to avoid using reflection on every call (due to Unconstrained Melody)
It is less code than the other solutions!
So, here are the steps to get this working:
In Package Manager Console, "Install-Package UnconstrainedMelody"
Add a property on your model like so:
//Replace "YourEnum" with the type of your enum
public IEnumerable<SelectListItem> AllItems
{
get
{
return Enums.GetValues<YourEnum>().Select(enumValue => new SelectListItem { Value = enumValue.ToString(), Text = enumValue.GetDescription() });
}
}
Now that you have the List of SelectListItem exposed on your model, you can use the #Html.DropDownList or #Html.DropDownListFor using this property as the source.
So many good answers - I thought I'sd add my solution - I am building the SelectList in the view (and not in the Controller):
In my c#:
namespace ControlChart.Models
//My Enum
public enum FilterType {
[Display(Name = "Reportable")]
Reportable = 0,
[Display(Name = "Non-Reportable")]
NonReportable,
[Display(Name = "All")]
All };
//My model:
public class ChartModel {
[DisplayName("Filter")]
public FilterType Filter { get; set; }
}
In my cshtml:
#using System.ComponentModel.DataAnnotations
#using ControlChart.Models
#model ChartMode
#*..........*#
#Html.DropDownListFor(x => x.Filter,
from v in (ControlChart.Models.FilterType[])(Enum.GetValues(typeof(ControlChart.Models.FilterType)))
select new SelectListItem() {
Text = ((DisplayAttribute)(typeof(FilterType).GetField(v.ToString()).GetCustomAttributes(typeof(DisplayAttribute), false).First())).Name,
Value = v.ToString(),
Selected = v == Model.Filter
})
HTH
Maybe is too late, but i think it could be useful for people with the same problem.
I've found here that now with MVC 5 it's included an EnumDropDownListFor html helper that makes for no longer necesary the use of custom helpers or other workarounds.
In this particular case, just add this:
#Html.EnumDropDownListFor(x => x.SelectedCurrency)
and that's all!
You can also translate or change the displayed text via data annotations and resources files:
Add the following data annotations to your enum:
public enum Currencies
{
[Display(Name="Currencies_CAD", ResourceType=typeof(Resources.Enums)]
CAD,
[Display(Name="Currencies_USD", ResourceType=typeof(Resources.Enums)]
USD,
[Display(Name="Currencies_EUR", ResourceType=typeof(Resources.Enums)]
EUR
}
Create the corresponding resources file.