Changing a value in a asp-route - entity-framework

With the code below is there a way to check if VendorName is not null and if so then asp-route-VendorName should be empty, the reason why I am asking is sometimes VendorName can be NULL and therefore the ToUpper() will fail. Hoping I can just do a conditional asp-route-VendorName
<a class="nav-item" asp-page="./Index" asp-route-VendorName="#vendor.VendorName.ToUpper()">
<i>#Html.DisplayFor(modelItem => vendor.VendorName)</i>
</a>

You can use IOutboundParmeterTransformer for this task (https://www.learnrazorpages.com/advanced/route-parameter-transformers). Create a class like this:
public class UpperCaseParameterTransformer : IOutboundParameterTransformer
{
public string TransformOutbound(object value)
{
return value?.ToString().ToUpper();
}
}
Register it with the ServiceContainer:
builder.Services.Configure<RouteOptions>(options =>
{
options.ConstraintMap.Add("uppercase", typeof(UpperCaseParameterTransformer));
});
Then apply it to a route value in the route template for the page:
#page "{VendorName:uppercase}"
That way, the routing system will take care of automatically converting any route parameter value to upper case.

Related

Index method not accepting parameters in ASP.NET Core MVC with Entity Framework

I am working on a webpage where I have to filter results using value from the select menu's selected option when the user clicks on filter button by sending a get parameter to the index method.
refId is never passed into the Index method
In your code, we can find that you specify asp-for="#Model.FirstOrDefault().RefereeId" for your <select> tag, if you check the source code of this dropdown in browser, you would find name attribute value is RefereeId (not refId) as below. So it would pass something like RefereeId=168 through querystring to your action, and parameter refId of your action would always be 0, which cause the issue.
To fix it, you can try to rename your action parameter to RefereeId, like below.
public async Task<IActionResult> Index(int RefereeId)
{
//code logic here
Or modify view page code to set name of <select> tag with "refId".
<select name="refId" asp-items="#ViewBag.PersonId"></select>
If you don't have a custom route defined, the default routing in asp.net core uses id.
Replace the refId parameter to id.
public async Task<IActionResult> Index(int id)
{
// ...
if (id != 0) //always remains 0
{
games = games.Where(g => g.RefereeId == id);
}
// ...
}

zend framework 1 Form - Note element lost its value if there is validation error

I have a Note element in my zend framework 1 form used for registration. It is defined in the format:
$captcha_reload = new Zend_Form_Element_Note('captcha_reload',
array('value'=>"<div id='captcha_reload_div'>
<a href='javascript:return false;'
id='change-image'>Change text.</a></div>"));
$this->addElement($captcha_reload);
This element displays a hyperlink and displays perfectly during registration page call.
The problem is during form submission. This note element doesn't displays anything (ie missing the hyperlink) if there is form validation error.
I have checked and tried the code below:
$this->setDefaults(array('captcha_reload'=>"<div id='captcha_reload_div'>
<a href='javascript:return false;'
id='change-image'>Change text.</a></div>"));
But still there is no value if there is form validation error.
For Note element, I have included the following in the Zend Registration Form page:
class Zend_Form_Element_Note extends Zend_Form_Element_Xhtml
{
public $helper = 'formNote';
}
When the form is submitted it is over-riding the value property of your element. As there is nothing being submitted, when the form is echoed again to show form errors, the value of the element is nothing as well.
Perhaps adding an isValid function to the element?
// pseudo-code
public function isValid($value, $context = null) {
$this->_setValue("<div id='captcha_reload_div'><a href='javascript:return false;' id='change-image'>Change text.</a></div>");
return true;
}
This will reset the value to your custom text, and return true without doing any checks (as you know the value is what you want it to be). Subsequently, when the form echos again it will show the value as set in isValid
class Zend_Form_Element_Note extends Zend_Form_Element_Xhtml
{
public $helper = 'formNote';
public function isValid($value, $context = null)
{
return true;
}
}
I have added that isValid() into Note class and it works fine. It doesn't need to use _setValue() inside Note class.

Play framework 2 : How to pass object between routes, views, and controller?

I'm trying to pass a book object from views to routes, and then send it to calculate in a controller. My code is following:
bookList.scala.html
#(books: java.lang.Iterable[Book])
#main("BookList"){
<div class="row">
#for(book <- books.iterator()){
<div class="col-sm-6 col-md-4">
<div class="thumbnail" style="height: 435px">
...
<p><a href="#routes.Application.buy(book)" class="btn btn-primary" role="button"
style="vertical-align:bottom">Order now!</a>
</div>
</div>
</div>
}
</div>
}
routes
...
GET /order controllers.Application.buy(book: models.Book)
...
However, It gave me an error : No QueryString binder found for type models.Book. Try to implement an implicit QueryStringBindable for this type.
I tried to change the routes as :
GET /order controllers.Application.buy(book)
It also returned an error :
type mismatch; found : String required: models.Book
That's not how Play routing works. The Play router parses variables from the URL or query string, and converts them to native types via the QueryBindable typeclass. You should have something more like this:
routes
GET /order/:bookid controllers.Application.buy(bookid: String)
And the action should be like:
Application.scala
def buy(bookid: String) = Action { request =>
// Look up book by id here.
Ok(views.html.thanks("you bought a book!"))
}
And the template like this:
bookList.scala.html
#for(book <- books.iterator()) {
...
<a href="#routes.Application.buy(book.id)" class="btn btn-primary"
}
Of course if your model's ID is other than String you need to modify the route's param type
Update -- alternative using form/POST
A form with a POST method is a better solution, or the user will buy another book each time they click the URL, and the id will be exposed. Check out the forms documentation. Your template would be like this:
#for(book <- books.iterator()) {
...
<form method="post">
<div>#book.name</div>
<input type="hidden" value="#book.id"/><button type="submit">buy</button>
</form>
}
You can't simple pass object (bean) as a url query parameter.
Default you can only define simple types as a parameter types. Please read carefully play's rounting documentation -> http://www.playframework.com/documentation/2.2.x/ScalaRouting especially Parameter types
But play framework has possibility to "learn" how to interpret specific url to bean.
You get info about this in error witch you posted. Responsible for this QueryStringBindable -> http://www.playframework.com/documentation/2.2.1/api/java/play/mvc/QueryStringBindable.html
In short like in documentation, when you define class like this:
class Book implements QueryStringBindable<Book> {
public String title;
public int numpages;
public Option<Pager> bind(String key, Map<String, String[]> data) {
if (data.contains(key + ".title" && data.contains(key + ".numpages") {
try {
title = data.get(key + ".title")[0];
numpages = Integer.parseInt(data.get(key + ".numpages")[0]);
return Some(this);
} catch (NumberFormatException e) {
return None();
}
} else {
return None();
}
}
public String unbind(String key) {
return key + ".title=" + title + "&" + key + ".numpages=" + numpages;
}
public String javascriptUnbind() {
return "function(k,v) {\n" +
" return encodeURIComponent(k+'.title')+'='+v.title+'&'+encodeURIComponent(k+'.numpages')+'='+v.numpages;\n" +
"}";
}
}
Then you can define route like:
GET /order controllers.Application.buy(p: Book)
You can then run in your browser e.g link:
localhost:9000/?p.title=SomeTitle&p.numpages=235
And in buy controller you will get p parameter as a Book class instance.
I did't test this code and this is in java. But you should get the idea.
You can define a custom PathBindable to automatically convert an id to a book object and pass it to buy method in controller. See PathBindable, another example
NOTE: I would define the PathBindable in same file as the Book model.
Simple example I used to parse in UUID's using PathBindable.
I'd rather not have to parse UUID's and ensure they're well formed in all controller/action code, so I use this at the router level. I used a UUIDP (P for param) to not pollute the UUID namespace.
Case Class and Parser
case class UUIDP(key: String, value: String, uuid: UUID)
object UUIDP {
implicit def pathBinder(implicit intBinder: PathBindable[String]) = new PathBindable[UUIDP] {
val uuidRegex = "([0-9A-Fa-f]{8}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{12})".r
override def bind(key: String, value: String): Either[String, UUIDP] = {
value match {
// sanity check, prevent errors, is this really a UUID?
case uuidRegex(c) => {
//error check too, binary value can be invalid
try { Right(UUIDP(key,value,UUID.fromString(value))) }
catch {
case ex : IllegalArgumentException => Left("Unparsable UUID")
}
}
case _ => Left("Invalid UUID Format")
}
}
// in case we need to remove this from the request
override def unbind(key: String, user: UUIDP): String = {
intBinder.unbind(key, user.value)
}
}
}
conf/routes
GET /items/:id #com.foo.MyController.getItem(id: UUIDP)
Controller
def getItem(sessionId: UUIDP) = Action(parse.text) { request =>
Ok(s"Fetching item [${sessionId.uuid}]")
}

Can my MVC2 app specify route constraints on Query String parameters?

My MVC2 app uses a component that makes subsequent AJAX calls back to the same action, which causes all kinds of unnecessary data access and processing on the server. The component vendor suggests I re-route those subsequent requests to a different action. The subsequent requests differ in that they have a particular query string, and I want to know whether I can put constraints on the query string in my route table.
For example, the initial request comes in with a URL like http://localhost/document/display/1. This can be handled by the default route. I want to write a custom route to handle URLs like http://localhost/document/display/1?vendorParam1=blah1&script=blah.js and http://localhost/document/display/1?vendorParam2=blah2&script=blah.js by detecting "vendor" in the URL.
I tried the following, but it throws a System.ArgumentException: The route URL cannot start with a '/' or '~' character and it cannot contain a '?' character.:
routes.MapRoute(
null,
"Document/Display/{id}?{args}",
new { controller = "OtherController", action = "OtherAction" },
new RouteValueDictionary { { "args", "vendor" } });
Can I write a route that takes the query string into account? If not, do you have any other ideas?
Update: Put simply, can I write routing constraints such that http://localhost/document/display/1 is routed to the DocumentController.Display action but http://localhost/document/display/1?vendorParam1=blah1&script=blah.js is routed to the VendorController.Display action? Eventually, I would like any URL whose query string contains "vendor" to be routed to the VendorController.Display action.
I understand the first URL can be handled by the default route, but what about the second? Is it possible to do this at all? After lots of trial and error on my part, it looks like the answer is "No".
QueryString parameters can be used in constraints, although it's not supported by default. Here you can find an article describing how to implement this in ASP.NET MVC 2.
As it is in Dutch, here's the implementation. Add an 'IRouteConstraint' class:
public class QueryStringConstraint : IRouteConstraint
{
private readonly Regex _regex;
public QueryStringConstraint(string regex)
{
_regex = new Regex(regex, RegexOptions.IgnoreCase);
}
public bool Match (HttpContextBase httpContext, Route route, string parameterName, RouteValueDictionary values, RouteDirection routeDirection)
{
// check whether the paramname is in the QS collection
if(httpContext.Request.QueryString.AllKeys.Contains(parameterName))
{
// validate on the given regex
return _regex.Match(httpContext.Request.QueryString[parameterName]).Success;
}
// or return false
return false;
}
}
Now you can use this in your routes:
routes.MapRoute("object-contact",
"{aanbod}",
/* ... */,
new { pagina = new QueryStringConstraint("some|constraint") });
You don't need a route for this. It is already handled by the default model binder. Query string parameters will be automatically bound to action arguments:
public ActionResult Foo(string id, string script, string vendorname)
{
// the id parameter will be bound from the default route token
// script and vendorname parameters will be bound from the request string
...
}
UPDATE:
If you don't know the name of the query string parameters that will be passed you could loop through them:
foreach (string key in Request.QueryString.Keys)
{
string value = Request.QueryString[key];
}
This post is old, but couldn't you write a route before your default route
this would only catch routes with "vendor" in the args
routes.MapRoute(
null,
"Document/Display/{id}?{args}",
new { controller = "VendorController", action = "OtherAction" },
new {args=#".*(vendor).*"}//believe this is correct regex to catch "vendor" anywhere in the args
);
And This would catch the rest
routes.MapRoute(
null,
"Document/Display/{id}?{args}",
new { controller = "DisplayController", action = "OtherAction" }
);
Haven't tried this and I am a novice to MVC but I believe this should work?? From what I understand if the constraint doesn't match the route isn't used. So it would test the next route. Since your next route doesn't use any constraint on the args, it should, match the route.
I tried this out and it worked for me.

How to prevent Url.RouteUrl(...) from inheriting route values from the current request

Lets say you have an action method to display products in a shopping cart
// ProductsController.cs
public ActionMethod Index(string gender) {
// get all products for the gender
}
Elsewhere, in a masthead that is displayed on every page you are using Url.RouteUrl to create HREF links to other pages on the site :
<a href="<%= Url.RouteUrl("testimonials-route", new { }) %>" All Testimonials </a>
This testimonials-route is defined in global.ascx by the first route below.
Notice that the above call to RouteUrl does not provide a gender, but the route is defined with a default of 'neutral' so we'd expect Testimonials.Index("neutral") to be called.
routes.MapRoute(
"testimonials-route",
"testimonials/{gender}",
new { controller = "Testimonials", action = "Index", gender = "neutral" },
new { gender = "(men|women|neutral)" }
);
routes.MapRoute(
"products-route",
"products/{gender}",
new { controller = "Products", action = "Index", gender = (string)null },
new { gender = "(men|women|neutral)" }
);
If someone visits the page /products/women we get an HREF to /testimonials/women
If someone visits the page /products then we get an empty HREF (the call to RouteUrl returns null).
But that doesn't make sense does it? testimonials-route is supposed to default to 'neutral' for us if we don't provide a route value for it?
What turns out is happening is that Url.RouteUrl(routeName, routeValues) helper extension will first look in its routeValues parameter for a gender route value and if it doesn't find it in that dictionary it will look at the current URL that we're on (remember that Url is a UrlHelper object which has the context of the current request available to it).
This has a possibly nice effect of giving us a link to men's testimonials if we're on a mens product page, but that probably isnt what we want if we haven't passed a value in the RouteUrl call, and explicitly specified 'neutral' as a default in the global.asax.cs file.
In the case where we visited /products/ we triggered the 'products-route' route and the Products(null) method was called. The call to Url.RouteUrl() actually inherits THIS null value for gender when we're creating a URL using testimonials-route. Even though we have specified a default for gender in 'testimionials-route' it still uses this null value which causes the route to fail and RouteUrl returns null. [note: the route fails because we have a constraint on (men|women|neutral) and null doesn't fit that]
It actually gets more scary - in that 'controller' and 'action' can be inherited in the same way. This can lead to URLs being generated to completely the wrong controller even when calling RouteUrl(...) with an explicit route name that has a default controller.
In this case once you've figured it out you can fix it quite easily in numerous ways, but it could in other cases cause some dangerous behavior. This may be by design, but its definitely scary.
My solution was this :
An HtmlExtension helper method :
public static string RouteUrl(this UrlHelper urlHelper, string routeName, object routeValues, bool inheritRouteParams)
{
if (inheritRouteParams)
{
// call standard method
return urlHelper.RouteUrl(routeName, routeValues);
}
else
{
// replace urlhelper with a new one that has no inherited route data
urlHelper = new UrlHelper(new RequestContext(urlHelper.RequestContext.HttpContext, new RouteData()));
return urlHelper.RouteUrl(routeName, routeValues);
}
}
I can now do :
Url.RouteUrl('testimonials-route', new { }, false)
and know for sure it will always behave the same way no matter what the context.
The way it works is to take the existing UrlHelper and create a new one with blank 'RouteData'. This means there is nothing to inherit from (even a null value).
Maybe I'm missing something here, but why not set it up like this:
In your global asax, create two routes:
routes.MapRoute(
"testimonial-mf",
"Testimonials/{gender}",
new { controller = "Testimonials", action = "Index" }
);
routes.MapRoute(
"testimonial-neutral",
"Testimonials/Neutral",
new { controller = "Testimonials", action = "Index", gender="Neutral" }
);
Then, use Url.Action instead of Url.RouteUrl:
<%= Url.Action("Testimonials", "Index") %>
<%= Url.Action("Testimonials", "Index", new { gender = "Male" }) %>
<%= Url.Action("Testimonials", "Index", new { gender = "Female" }) %>
Which, on my machine resolves to the following urls:
/Testimonials/Neutral
/Testimonials/Male
/Testimonials/Female
I'm not sure if this is an acceptable solution, but it's an alternative, no?