Spring MVC 3 - optional URL parameter - spring

I've written following code:
#Controller
#RequestMapping("/page{number}")
public class IndexController
{
#RequestMapping(method = RequestMethod.GET)
public String printIndex(ModelMap model, #PathVariable int number)
{
String numberText;
switch (number)
{
case 0:
numberText = "Zero";
break;
case 1:
numberText = "One";
break;
default:
numberText = "Unknown";
break;
}
model.addAttribute("number", numberText);
return "page";
}
}
And I'm tring to achieve URLs like page1.html, page2.html, page3.html controlled by this method, with one exception: page.html should give same result as page1.html. I'm looking for something to make {number} optional, now it's required.
Is there a way to do that at I said?
/

You can use something like this:
#RequestParam(value = "name", defaultValue = "") Long name
Keep in mind that you must use the wrappers (like Long) and not the primitives ones (like long).
I hope this will be useful.

How about this:
#Controller
public class IndexController
{
#RequestMapping("/page{number}")
public String printIndex(ModelMap model, #PathVariable("number") int number)
{
String numberText;
switch (number)
{
case 0:
numberText = "Zero";
break;
case 1:
numberText = "One";
break;
default:
numberText = "Unknown";
break;
}
model.addAttribute("number", numberText);
return "page";
}
#RequestMapping("/page")
public String printIndex(ModelMap model)
{
return printIndex(model, 1);
}
}

you might want to implement a custom WebArgumentResolver and annotation #OptionalPathVariable and handle it yourself

Rest API - Optional Parameters and change values
#GetMapping(value = "/country/list")
public ResponseEntity<?> companyInformationList
(
Pageable pageable,
#RequestParam(name = "q", required = false,defaultValue = "") String q,
#RequestParam(name = "sortby", required = false, defaultValue = "companyId") String sortby,
#RequestParam(name = "order", required = false, defaultValue = "desc") String order,
#RequestHeader(value = "Accept-Language", defaultValue = "ar") String lang ) {
if(sortby.equalsIgnoreCase("countryName")) {
if(lang.equalsIgnoreCase("en")) {
sortby="countryNameEn";
}else
if(lang.equalsIgnoreCase("ar")) {
sortby="countryNameAr";
}else
if(lang.equalsIgnoreCase("fr")) {
sortby="countryNameFr";
}

Related

ASP.NET MVC validation return lowercase property name

In my ASP.NET MVC Core web application the Json serialization of properties is set to camel case (with first letter lowercase):
services.AddMvc()
.SetCompatibilityVersion(CompatibilityVersion.Version_2_1)
.AddJsonOptions(opt =>
{
opt.SerializerSettings.ContractResolver = new DefaultContractResolver { NamingStrategy = new CamelCaseNamingStrategy() };
opt.SerializerSettings.Converters.Add(new StringEnumConverter(true));
});
The serialization to the client is working as expected.
But when the javascript client tries to post data and this data is not valid, he receives a validation message with capital letter properties, this validation messages are the ModelState:
{"Info":["The Info field is required."]}
Is there a way to make ASP.NET return lowercase property in validation messages of the ModelState to reflect the naming strategy?
The solution is to disable the automatic api validation filter and create own json result with the validation messages:
services.Configure<ApiBehaviorOptions>(options =>
{
options.SuppressModelStateInvalidFilter = true;
});
And in the controller:
protected ActionResult ValidationFailed()
{
var errorList = ModelState.ToDictionary(
kvp => kvp.Key.ToCamelCase(),
kvp => kvp.Value.Errors.Select(e => e.ErrorMessage).ToArray()
);
return BadRequest(errorList);
}
public async Task<ActionResult> Create([FromBody]TCreateDto model)
{
if (ModelState.IsValid == false)
{
return ValidationFailed();
}
...
}
The string helper method:
public static string ToCamelCase(this string name)
{
if (string.IsNullOrEmpty(name))
{
return name;
}
return name.Substring(0, 1).ToLower() + name.Substring(1);
}
There is an easier solution. Use Fluent Validator's ValidatorOptions.Global.PropertyNameResolver. Taken from here and converted to C# 8 and Fluent Validation 9:
In Startup.cs, ConfigureServices use:
services
.AddControllers()
.SetCompatibilityVersion(CompatibilityVersion.Version_3_0)
.AddFluentValidation(fv =>
{
fv.RegisterValidatorsFromAssemblyContaining<MyValidator>();
// Convert property names to camelCase as Asp.Net Core does https://github.com/FluentValidation/FluentValidation/issues/226
ValidatorOptions.Global.PropertyNameResolver = CamelCasePropertyNameResolver.ResolvePropertyName;
})
.AddNewtonsoftJson(NewtonsoftUtils.SetupNewtonsoftOptionsDefaults);
and resolver itself:
/// <summary>
/// Convert property names to camelCase as Asp.Net Core does
/// https://github.com/FluentValidation/FluentValidation/issues/226
/// </summary>
public class CamelCasePropertyNameResolver
{
public static string? ResolvePropertyName(Type type, MemberInfo memberInfo, LambdaExpression expression)
{
return ToCamelCase(DefaultPropertyNameResolver(type, memberInfo, expression));
}
private static string? DefaultPropertyNameResolver(Type type, MemberInfo memberInfo, LambdaExpression expression)
{
if (expression != null)
{
var chain = PropertyChain.FromExpression(expression);
if (chain.Count > 0)
{
return chain.ToString();
}
}
if (memberInfo != null)
{
return memberInfo.Name;
}
return null;
}
private static string? ToCamelCase(string? s)
{
if (string.IsNullOrEmpty(s) || !char.IsUpper(s[0]))
{
return s;
}
var chars = s.ToCharArray();
for (var i = 0; i < chars.Length; i++)
{
if (i == 1 && !char.IsUpper(chars[i]))
{
break;
}
var hasNext = (i + 1 < chars.Length);
if (i > 0 && hasNext && !char.IsUpper(chars[i + 1]))
{
break;
}
chars[i] = char.ToLower(chars[i], CultureInfo.InvariantCulture);
}
return new string(chars);
}
}
I have faced the same issue. I have overridden DefaultProblemDetailsFactory.cs from the source code and add logic to change the first letters in the 'errors' dictionary.
Steps:
1 - Create new CustomProblemDetailsFactory.cs class:
internal sealed class CustomProblemDetailsFactory : ProblemDetailsFactory
{
private readonly ApiBehaviorOptions _options;
public CustomProblemDetailsFactory(IOptions<ApiBehaviorOptions> options)
{
_options = options?.Value ?? throw new ArgumentNullException(nameof(options));
}
public override ProblemDetails CreateProblemDetails(
HttpContext httpContext,
int? statusCode = null,
string? title = null,
string? type = null,
string? detail = null,
string? instance = null)
{
statusCode ??= 500;
var problemDetails = new ProblemDetails
{
Status = statusCode,
Title = title,
Type = type,
Detail = detail,
Instance = instance,
};
ApplyProblemDetailsDefaults(httpContext, problemDetails, statusCode.Value);
return problemDetails;
}
public override ValidationProblemDetails CreateValidationProblemDetails(
HttpContext httpContext,
ModelStateDictionary modelStateDictionary,
int? statusCode = null,
string? title = null,
string? type = null,
string? detail = null,
string? instance = null)
{
if (modelStateDictionary == null)
{
throw new ArgumentNullException(nameof(modelStateDictionary));
}
statusCode ??= 400;
var problemDetails = new ValidationProblemDetails(modelStateDictionary)
{
Status = statusCode,
Type = type,
Detail = detail,
Instance = instance,
};
if (title != null)
{
// For validation problem details, don't overwrite the default title with null.
problemDetails.Title = title;
}
// FIX LOWERCASE, MAKE THE FIRST LETTERS LOWERCASE
///-----------------------------
if (problemDetails.Errors != null)
{
var newErrors = problemDetails.Errors.ToDictionary(x => this.MakeFirstLetterLowercase(x.Key), x => x.Value);
problemDetails.Errors.Clear();
foreach (var keyValue in newErrors)
{
problemDetails.Errors.Add(keyValue.Key, keyValue.Value);
}
}
///-----------------------------
ApplyProblemDetailsDefaults(httpContext, problemDetails, statusCode.Value);
return problemDetails;
}
private void ApplyProblemDetailsDefaults(HttpContext httpContext, ProblemDetails problemDetails, int statusCode)
{
problemDetails.Status ??= statusCode;
if (_options.ClientErrorMapping.TryGetValue(statusCode, out var clientErrorData))
{
problemDetails.Title ??= clientErrorData.Title;
problemDetails.Type ??= clientErrorData.Link;
}
var traceId = Activity.Current?.Id ?? httpContext?.TraceIdentifier;
if (traceId != null)
{
problemDetails.Extensions["traceId"] = traceId;
}
}
private string MakeFirstLetterLowercase(string str)
{
if (!string.IsNullOrEmpty(str) && char.IsUpper(str[0]))
{
return str.Length == 1 ? char.ToLower(str[0]).ToString() : char.ToLower(str[0]) + str[1..];
}
return str;
}
}
2 - In the Startup.cs override the default ProblemDetailsFactory:
services.AddSingleton<ProblemDetailsFactory, CustomProblemDetailsFactory>();
After that all keys in the dictionary 'errors' will start with lowercase

Dealing with one field that is sometimes boolean and sometimes int

I'm trying to work with the reddit JSON API. There are post data objects that contain a field called edited which may contain a boolean false if the post hasn't been edited, or a timestamp int if the post was edited.
Sometimes a boolean:
{
"edited": false,
"title": "Title 1"
}
Sometimes an int:
{
"edited": 1234567890,
"title": "Title 2"
}
When trying to parse the JSON where the POJO has the field set to int, I get an error: JsonDataException: Expected an int but was BOOLEAN...
How can I deal with this using Moshi?
I also ran into a similar problem where I had fields that were sometimes booleans, and sometimes ints. I wanted them to always be ints. Here's how I solved it with Moshi and kotlin:
Make a new annotation that you will use on fields to should convert from boolean to int
#JsonQualifier
#Retention(AnnotationRetention.RUNTIME)
#Target(AnnotationTarget.FIELD, AnnotationTarget.VALUE_PARAMETER, AnnotationTarget.FUNCTION)
annotation class ForceToInt
internal class ForceToIntJsonAdapter {
#ToJson
fun toJson(#ForceToInt i: Int): Int {
return i
}
#FromJson
#ForceToInt
fun fromJson(reader: JsonReader): Int {
return when (reader.peek()) {
JsonReader.Token.NUMBER -> reader.nextInt()
JsonReader.Token.BOOLEAN -> if (reader.nextBoolean()) 1 else 0
else -> {
reader.skipValue() // or throw
0
}
}
}
}
Use this annotation on the fields that you want to force to int:
#JsonClass(generateAdapter = true)
data class Discovery(
#Json(name = "id") val id: String = -1,
#ForceToInt #Json(name = "thanked") val thanked: Int = 0
)
The easy way might be to make your Java edited field an Object type.
The better way for performance, error catching, and appliaction usage is to use a custom JsonAdapter.
Example (edit as needed):
public final class Foo {
public final boolean edited;
public final int editedNumber;
public final String title;
public static final Object JSON_ADAPTER = new Object() {
final JsonReader.Options options = JsonReader.Options.of("edited", "title");
#FromJson Foo fromJson(JsonReader reader) throws IOException {
reader.beginObject();
boolean edited = true;
int editedNumber = -1;
String title = "";
while (reader.hasNext()) {
switch (reader.selectName(options)) {
case 0:
if (reader.peek() == JsonReader.Token.BOOLEAN) {
edited = reader.nextBoolean();
} else {
editedNumber = reader.nextInt();
}
break;
case 1:
title = reader.nextString();
break;
case -1:
reader.nextName();
reader.skipValue();
default:
throw new AssertionError();
}
}
reader.endObject();
return new Foo(edited, editedNumber, title);
}
#ToJson void toJson(JsonWriter writer, Foo value) throws IOException {
writer.beginObject();
writer.name("edited");
if (value.edited) {
writer.value(value.editedNumber);
} else {
writer.value(false);
}
writer.name("title");
writer.value(value.title);
writer.endObject();
}
};
Foo(boolean edited, int editedNumber, String title) {
this.edited = edited;
this.editedNumber = editedNumber;
this.title = title;
}
}
Don't forget to register the adapter on your Moshi instance.
Moshi moshi = new Moshi.Builder().add(Foo.JSON_ADAPTER).build();
JsonAdapter<Foo> fooAdapter = moshi.adapter(Foo.class);

Can I override/delegate assignment operator?

I began learn Groovy, and faced the challenge.
I have this code, that stores meta-data to object:
class Meta {
final MetaItem name
final MetaItem description
// ...
// And more fields with type MetaItem
// ...
Meta() {
name = new MetaItem("name")
description = new MetaItem("description")
}
void setName(String name) {
this.name.value = name
}
String getName() {
return this.name.value
}
void setDescription(String description) {
this.description.value = description
}
String getDescription() {
return this.description.value
}
// ...
// And more methods. Two for each field
// ...
}
class MetaItem {
private final def id
def value
MetaItem(String id) {
this.id = id
}
}
// Validating
def meta = new Meta()
assert meta.name == null
assert meta.description == null
meta.with {
name = "Name"
description = "Desc"
}
assert meta.name == "Name"
assert meta.description == "Desc"
print "Success!"
As you can see from the code, it increases quicly in volumes when new fields are added, because for each field you need to add two methods. Can this somehow be optimized? Redirect the assignment operation from object to his member. I've looked on Delegate, but this is not what I need.
P.S. I can't use access by .value because this class is used in Gradle extension and I need to configure it like this:
myExtension {
meta {
name = "Name"
description = "Desc"
// And many others
}
}
P.P.S. Sorry for my bad english, it's not my first language

Custom fields with FormBuilder in the Microsoft Bot Framework - not working

I tried this solution: Custom fields with FormBuilder in the Microsoft Bot Framework
But failed to get it working....The problem I encountered is that when I assign the base.Form = value, the _prompt in the _field gets a default recognizer, and it won't get overriden in the next line's SetRecognizer call, that only replaces the _field's recognizer.
However the matching process uses the _prompt's recognizer internally ( ? ).
Here is my code:
public class LuisIntentRecognizer<T> : RecognizePrimitive<T>
where T : class
{
public LuisIntentRecognizer(IField<T> field, string luisModelID, string luisSubscriptionKey)
: base(field)
{
_luisModelID = luisModelID;
_luisSubscriptionKey = luisSubscriptionKey;
}
public override DescribeAttribute ValueDescription(object value)
{
return new DescribeAttribute((string)value);
}
public override IEnumerable<string> ValidInputs(object value)
{
yield return (string)value;
}
public override TermMatch Parse(string input)
{
TermMatch result = null;
if (!string.IsNullOrWhiteSpace(input))
{
var luisModel = new LuisModelAttribute(_luisModelID, _luisSubscriptionKey);
var luisService = new LuisService(luisModel);
var luisResult = luisService.QueryAsync(input).Result; // TODO refactor somehow to async
var winner = luisResult.Intents.MaxBy(i => i.Score ?? 0d);
if (winner != null && !string.IsNullOrEmpty(winner.Intent))
{
result = new TermMatch(0, winner.Intent.Length, 0.0, winner.Intent);
}
else
{
result = new TermMatch(0, input.Length, 0.0, input);
}
}
return result;
}
public override string Help(T state, object defaultValue)
{
var prompt = new Prompter<T>(_field.Template(TemplateUsage.StringHelp), _field.Form, null);
var args = HelpArgs(state, defaultValue);
return prompt.Prompt(state, _field.Name, args.ToArray()).Prompt;
}
private string _luisModelID;
private string _luisSubscriptionKey;
}
public class LuisIntentField<T> : FieldReflector<T>
where T : class
{
public LuisIntentField(string name, string luisModelID, string luisSubscriptionKey, bool ignoreAnnotations = false)
: base(name, ignoreAnnotations)
{
_luisModelID = luisModelID;
_luisSubscriptionKey = luisSubscriptionKey;
}
public override IForm<T> Form
{
set
{
base.Form = value;
base.SetRecognizer(new LuisIntentRecognizer<T>(this, _luisModelID, _luisSubscriptionKey));
}
}
private string _luisModelID;
private string _luisSubscriptionKey;
}
Could anyone get it working?
Thanks
It seems to be a bug in the framework indeed: https://github.com/Microsoft/BotBuilder/issues/879

Creating Canonical URLs including an id and title slug

I want to replicate what StackOverflow does with its URLs.
For example:
Hidden Features of C#? - (Hidden Features of C#?)
or
Hidden Features of C#? - (Hidden Features of C#?)
Will Take you to the same page but when they return to the browser the first one is always returned.
How do you implement the change so the larger URL is returned?
The way that I've handled this before is to have two routes, registered in this order
routes.MapRoute(
null,
"questions/{id}/{title}",
new { controller = "Questions", action = "Index" },
new { id = #"\d+", title = #"[\w\-]*" });
routes.MapRoute(
null,
"questions/{id}",
new { controller = "Questions", action = "Index" },
new { id = #"\d+" });
now in the controller action,
public class QuestionsController
{
private readonly IQuestionRepository _questionRepo;
public QuestionsController(IQuestionRepository questionRepo)
{
_questionRepo = questionRepo;
}
public ActionResult Index(int id, string title)
{
var question = _questionRepo.Get(id);
if (string.IsNullOrWhiteSpace(title) || title != question.Title.ToSlug())
{
return RedirectToAction("Index", new { id, title = question.Title.ToSlug() }).AsMovedPermanently();
}
return View(question);
}
}
We'll permanently redirect to the URL that contains the title slug (lowercase title with hyphens as separators) if we only have the id. We also make sure that the title passed is the correct one by checking it against the slugged version of the question title, thereby creating a canonical URL for the question that contains both the id and the correct title slug.
A couple of the helpers used
public static class PermanentRedirectionExtensions
{
public static PermanentRedirectToRouteResult AsMovedPermanently
(this RedirectToRouteResult redirection)
{
return new PermanentRedirectToRouteResult(redirection);
}
}
public class PermanentRedirectToRouteResult : ActionResult
{
public RedirectToRouteResult Redirection { get; private set; }
public PermanentRedirectToRouteResult(RedirectToRouteResult redirection)
{
this.Redirection = redirection;
}
public override void ExecuteResult(ControllerContext context)
{
// After setting up a normal redirection, switch it to a 301
Redirection.ExecuteResult(context);
context.HttpContext.Response.StatusCode = 301;
context.HttpContext.Response.Status = "301 Moved Permanently";
}
}
public static class StringExtensions
{
private static readonly Encoding Encoding = Encoding.GetEncoding("Cyrillic");
public static string RemoveAccent(this string value)
{
byte[] bytes = Encoding.GetBytes(value);
return Encoding.ASCII.GetString(bytes);
}
public static string ToSlug(this string value)
{
if (string.IsNullOrWhiteSpace(value))
{
return string.Empty;
}
var str = value.RemoveAccent().ToLowerInvariant();
str = Regex.Replace(str, #"[^a-z0-9\s-]", "");
str = Regex.Replace(str, #"\s+", " ").Trim();
str = str.Substring(0, str.Length <= 200 ? str.Length : 200).Trim();
str = Regex.Replace(str, #"\s", "-");
str = Regex.Replace(str, #"-+", "-");
return str;
}
}

Resources