My test url:
localhost:61578/?type=promotion&foo=bar
I usually use this way to get the value of type parameter:
public IActionResult Index(string type)
{
// type = "promotion"
}
My question: How to detect all parameters in the url? I want to prevent to access the page with some unknown parameter.
Something like this:
public IActionResult Index(string type, string foo)
{
if (!string.IsNullOrEmpty(foo))
{
return BadRequest(); // page 404
}
}
The problem is: I don't know exactly the name what user enters. So, it can be:
localhost:61578/?bar=baz&type=promotion
You can use the HttpContext Type to grab the query string
var context = HttpContext.Current;
then, you can grab the entire query string:
var queryString = context.Request.QueryString
// "bar=baz&type=promotion"
or, you can get a list of objects:
var query = context.Request.Query.Select(_ => new
{
Key = _.Key.ToString(),
Value = _.Value.ToString()
});
// { Key = "bar", Value = "baz" }
// { Key = "type", Value = "promotion" }
or, you could make a dictionary:
Dictionary<string, string>queryKvp = context.Request.GetQueryNameValuePairs()
.ToDictionary(_=> _.Key, _=> _.Value, StringComparer.OrdinalIgnoreCase);
// queryKvp["bar"] = "baz"
// queryKvp["type"] = "promotion"
Related
I have a default route specified like this:
routes.MapRoute(
"Default", // Route name
"{controller}/{action}/{id}", // URL with parameters
new { controller = "Home", action = "Index", id = "", RouteName = "Default" }, // Parameter defaults
new { controller = #"[^\.]*" } // Constraints (Ignore urls with periods in them)
);
I have a controller called Test and an action on Test called DoSomething that is defined like this:
public ActionResult DoSomething(int someId, int someotherId, IEnumerable<string> listOfSomething, bool flag, bool flag2)
I am trying to call the action like this:
var parameters = new RouteValueDictionary();
parameters.Add("someId", id);
parameters.Add("someotherId", otherId);
parameters.Add("flag", flag1);
parameters.Add("flag2", flag2);
for (int i = 0; i < thisList.Count; i++)
{
parameters.Add("listOfSomething[" + i + "]", thisList[i]);
}
Html.RenderAction("DoSomething", "Test", parameters);
The Html.RenderAction call is failing with the InvalidOperationException : No route in the route table matches the supplied values.
What would be causing this? The default route should pick this call up?
No route in the route table matches the supplied values indicates that no valid route in RouteCollection matches the requested URL, i.e. your default route parameters count doesn't match with DoSomething action parameters (1 parameter : 5 parameters).
Also note that IEnumerable<string> considered as complex object, therefore you can't pass it as part of RouteValueDictionary parameters in URL. Hence, you should pass only 4 value parameters & pass IEnumerable<string> object as Session or TempData content.
First, define a custom route with 4 parameters as such on top of default one (avoid modifying default route which placed last in route order, it may required for other routes):
routes.MapRoute(
"Custom", // Route name
"{controller}/{action}/{someId}/{someotherId}/{flag}/{flag2}", // URL with parameters
new { controller = "Home", action = "Index", someId = "", someotherId = "", flag = "", flag2 = "" }, // Parameter defaults
new { controller = #"[^\.]*" } // Constraints (Ignore urls with periods in them)
);
Then, edit controller action method to receive 4 parameters (strip off IEnumerable<string> from parameters list, based from explanation above):
public class TestController : Controller
{
public ActionResult DoSomething(int someId, int someotherId, bool flag, bool flag2)
{
// other stuff
}
}
And pass parameters for redirect thereafter, with IEnumerable object stored in TempData or Session variable:
var parameters = new RouteValueDictionary();
parameters.Add("someId", id);
parameters.Add("someotherId", otherId);
parameters.Add("flag", flag1);
parameters.Add("flag2", flag2);
var list = new List<string>();
for (int i = 0; i < thisList.Count; i++)
{
list.Add(thisList[i]);
}
TempData["listOfSomething"] = list;
Html.RenderAction("DoSomething", "Test", parameters);
Or define parameters directly in RedirectToAction:
var list = new List<string>();
for (int i = 0; i < thisList.Count; i++)
{
list.Add(thisList[i]);
}
TempData["listOfSomething"] = list;
Html.RenderAction("DoSomething", "Test", new { someId = id, someotherId = otherId, flag = flag1, flag2 = flag2 });
If thisList is already IEnumerable<string> instead, remove for loop & just assign it straight to Session/TempData:
TempData["listOfSomething"] = thislist;
The list of parameters can be retrieved using this way:
var listOfParameters = TempData["listOfSomething"] as List<string>;
Similar issues as reference:
How to pass List in Redirecttoaction
Sending a list using RedirectToAction in MVC4
Passing an array or list of strings from one action to another when redirecting
Routing with Multiple Parameters using ASP.NET MVC
How to pass multiple objects using RedirectToAction() in Asp.NET MVC?
how can I get a response after I created a data? So I want is when is saves. it show it's response, maybe in messagebox? Is it possible do it?
This is my controller code in saving..
[HttpPost]
[ValidateAntiForgeryToken]
public async System.Threading.Tasks.Task<ActionResult> Create(FormCollection formCollection, string fn, string ln , ParentModel apsp)
{
string username = "sa";
string apiKey = "sa";
string baseUrl = "https://sandbox-api.paysimple.com";
var settings = new PaySimpleSdk.Models.PaySimpleSettings(apiKey, username, baseUrl);
var paymentService = new PaymentService(settings);
fn = apsp.Customer.FirstName;
ln = apsp.Customer.LastName;
string street1 = apsp.Customer.BillingAddress.StreetAddress1;
string street2 = apsp.Customer.BillingAddress.StreetAddress2;
string city = apsp.Customer.BillingAddress.City;
Enum statecode = apsp.Customer.BillingAddress.StateCode;
Enum country = apsp.Customer.BillingAddress.Country;
string zipcode = apsp.Customer.BillingAddress.ZipCode;
string credit = apsp.CreditCard.CreditCardNumber;
string expir = apsp.CreditCard.ExpirationDate;
Enum issuer = apsp.CreditCard.Issuer;
decimal amount = apsp.Payment.Amount;
string ccv = apsp.Payment.Cvv;
var customerPayment = new NewCustomerPayment<CreditCard>
{
Customer = new Customer()
{
FirstName = fn,
LastName = ln,
BillingAddress = new Address
{
StreetAddress1 = street1,
StreetAddress2 = street2,
City = city,
StateCode = (StateCode)statecode,
Country = (CountryCode)country,
ZipCode = zipcode
}
},
Account = new CreditCard
{
CreditCardNumber = credit,
ExpirationDate = expir,
Issuer = (Issuer)issuer
},
Payment = new Payment
{
Amount = amount,
Cvv = ccv
}
};
var newCustomerPayment = await paymentService.CreateNewCustomerPaymentAsync(customerPayment);
return RedirectToAction("Index");
}
The function of creating a data is from the SDK and the model itself
You have 2 options:
Create action should return JsonResult instead of redirect. But you will need to use AJAX when calling Create action.:
public async System.Threading.Tasks.Task<ActionResult> Create(FormCollection formCollection, string fn, string ln , ParentModel apsp)
{
/// your code for creating object
return Json(newCustomerPayment, JsonRequestBehavior.AllowGet);
}
on the client side use Ajax.BeginForm instead of Html.BeginForm
using (Ajax.BeginForm("Create", Home , new AjaxOptions { HttpMethod = "POST", OnSuccess = "customerPaymentCreatedSuccess" }))
{
}
<script>
function customerPaymentCreatedSuccess(response)
{
alert(JSON.stringify(response, null, 4));
}
</script>
Use Post/Redirect/Get pattern. Once new payment is created, store it in TempData
and return redirect as you currently do:
TempData["newCustomerPayment"] = newCustomerPayment;
return RedirectToAction("Index");
Then in Index Action check if there is anything in TempData and if there is, pass it to the view
public ActionResult Index()
{
var customerPayment = TempData["newCustomerPayment"] as NewCustomerPayment;
if(customerPayment != null)
{
ViewBag.customerPayment = customerPayment;
}
//other code..
}
Index view - generate JavaScript code to display customerPayment:
#{var customerPayment = ViewBag.customerPayment as NewCustomerPayment;}
#if(customerPayment != null)
{
<script>
alert("#Html.Raw(JsonConvert.SerializeObject(customerPayment ))");
</script>
}
To show an feedback of your of your operation you could return a JsonResult from your method and make the call and create the message box browser-side via Javascript.
Actions to take
1)change ActionResult to JsonResult in method definition
public async System.Threading.Tasks.Task<JsonResult> Create(FormCollection formCollection, string fn, string ln , ParentModel apsp)
2)change the return to something like:
return this.Json(message)
3)make your call to the method using ajax an create the message box on the callback method
I am new to ASP.Net MVC and facing a problem. Here it is.
routes.MapRoute(
"SearchResults",// Route name
"{controller}/{action}/{category}/{manufacturer}/{attribute}",
new {
controller = "Home",
action = "CategoryProducts",
category = UrlParameter.Optional,
manufacturer = UrlParameter.Optional,
attribute = UrlParameter.Optional
}
);
And here is my controller method.
public ActionResult CategoryProducts(string category, string manufacturer, string attribute)
{
string[] categoryParameter = category.Split('_');
.
.
.
return View();
}
when i hit the url i always get null in category parameter
http://localhost:50877/Home/CategoryProducts/c_50_ShowcasesDisplays
I get this error
Object reference not set to an instance of an object
How can i fix this problem. I need to extract the id from segment and use it. Similarly i need to process the manufacturer and attribute strings too.
One more thing
How can i make my function get at least one parameter regardless of order? I mean i want to make functions like that i can handle category or manufacturer or attributes or category + manufacturer and all the combinations/
A placeholder (such as {category}) acts like a variable - it can contain any value. The framework must be able to understand what the parameters in the URL mean. You can do this one of three ways:
Provide them in a specific order, and for a specific number of segments
Put them in the query string so you have name/value pairs to identify what they are
Make a series of routes with literal segments to provide names to identify what the parameters are
Here is an example of option #3. It is a bit involved compared to using query string parameters, but it is certainly possible as long as you provide some sort of identifier for each route segment.
IEnumerable Extensions
This adds LINQ support for being able to get every possible permutation of parameter values.
using System;
using System.Collections.Generic;
using System.Linq;
public static class IEnumerableExtensions
{
// Can be used to get all permutations at a certain level
// Source: http://stackoverflow.com/questions/127704/algorithm-to-return-all-combinations-of-k-elements-from-n#1898744
public static IEnumerable<IEnumerable<T>> Combinations<T>(this IEnumerable<T> elements, int k)
{
return k == 0 ? new[] { new T[0] } :
elements.SelectMany((e, i) =>
elements.Skip(i + 1).Combinations(k - 1).Select(c => (new[] { e }).Concat(c)));
}
// This one came from: http://stackoverflow.com/questions/774457/combination-generator-in-linq#12012418
private static IEnumerable<TSource> Prepend<TSource>(this IEnumerable<TSource> source, TSource item)
{
if (source == null)
throw new ArgumentNullException("source");
yield return item;
foreach (var element in source)
yield return element;
}
public static IEnumerable<IEnumerable<TSource>> Permutations<TSource>(this IEnumerable<TSource> source)
{
if (source == null)
throw new ArgumentNullException("source");
var list = source.ToList();
if (list.Count > 1)
return from s in list
from p in Permutations(list.Take(list.IndexOf(s)).Concat(list.Skip(list.IndexOf(s) + 1)))
select p.Prepend(s);
return new[] { list };
}
}
RouteCollection Extensions
We extend the MapRoute extension method, adding the ability to add a set of routes to match all possible permutations of the URL.
using System;
using System.Collections.Generic;
using System.Web.Mvc;
using System.Web.Routing;
public static class RouteCollectionExtensions
{
public static void MapRoute(this RouteCollection routes, string url, object defaults, string[] namespaces, string[] optionalParameters)
{
MapRoute(routes, url, defaults, null, namespaces, optionalParameters);
}
public static void MapRoute(this RouteCollection routes, string url, object defaults, object constraints, string[] namespaces, string[] optionalParameters)
{
if (routes == null)
{
throw new ArgumentNullException("routes");
}
if (url == null)
{
throw new ArgumentNullException("url");
}
AddAllRoutePermutations(routes, url, defaults, constraints, namespaces, optionalParameters);
}
private static void AddAllRoutePermutations(RouteCollection routes, string url, object defaults, object constraints, string[] namespaces, string[] optionalParameters)
{
// Start with the longest routes, then add the shorter ones
for (int length = optionalParameters.Length; length > 0; length--)
{
foreach (var route in GetRoutePermutations(url, defaults, constraints, namespaces, optionalParameters, length))
{
routes.Add(route);
}
}
}
private static IEnumerable<Route> GetRoutePermutations(string url, object defaults, object constraints, string[] namespaces, string[] optionalParameters, int length)
{
foreach (var combination in optionalParameters.Combinations(length))
{
foreach (var permutation in combination.Permutations())
{
yield return GenerateRoute(url, permutation, defaults, constraints, namespaces);
}
}
}
private static Route GenerateRoute(string url, IEnumerable<string> permutation, object defaults, object constraints, string[] namespaces)
{
var newUrl = GenerateUrlPattern(url, permutation);
var result = new Route(newUrl, new MvcRouteHandler())
{
Defaults = CreateRouteValueDictionary(defaults),
Constraints = CreateRouteValueDictionary(constraints),
DataTokens = new RouteValueDictionary()
};
if ((namespaces != null) && (namespaces.Length > 0))
{
result.DataTokens["Namespaces"] = namespaces;
}
return result;
}
private static string GenerateUrlPattern(string url, IEnumerable<string> permutation)
{
string result = url;
foreach (string param in permutation)
{
result += "/" + param + "/{" + param + "}";
}
System.Diagnostics.Debug.WriteLine(result);
return result;
}
private static RouteValueDictionary CreateRouteValueDictionary(object values)
{
IDictionary<string, object> dictionary = values as IDictionary<string, object>;
if (dictionary != null)
{
return new RouteValueDictionary(dictionary);
}
return new RouteValueDictionary(values);
}
}
Usage
public class RouteConfig
{
public static void RegisterRoutes(RouteCollection routes)
{
routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
routes.MapRoute(
url: "Home/CategoryProducts",
defaults: new { controller = "Home", action = "CategoryProducts" },
namespaces: null,
optionalParameters: new string[] { "category", "manufacturer", "attribute" });
routes.MapRoute(
name: "Default",
url: "{controller}/{action}/{id}",
defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional }
);
}
}
This adds a complete set of routes to match the URL patterns:
Home/CategoryProducts/category/{category}/manufacturer/{manufacturer}/attribute/{attribute}
Home/CategoryProducts/category/{category}/attribute/{attribute}/manufacturer/{manufacturer}
Home/CategoryProducts/manufacturer/{manufacturer}/category/{category}/attribute/{attribute}
Home/CategoryProducts/manufacturer/{manufacturer}/attribute/{attribute}/category/{category}
Home/CategoryProducts/attribute/{attribute}/category/{category}/manufacturer/{manufacturer}
Home/CategoryProducts/attribute/{attribute}/manufacturer/{manufacturer}/category/{category}
Home/CategoryProducts/category/{category}/manufacturer/{manufacturer}
Home/CategoryProducts/manufacturer/{manufacturer}/category/{category}
Home/CategoryProducts/category/{category}/attribute/{attribute}
Home/CategoryProducts/attribute/{attribute}/category/{category}
Home/CategoryProducts/manufacturer/{manufacturer}/attribute/{attribute}
Home/CategoryProducts/attribute/{attribute}/manufacturer/{manufacturer}
Home/CategoryProducts/category/{category}
Home/CategoryProducts/manufacturer/{manufacturer}
Home/CategoryProducts/attribute/{attribute}
Now when you use the following URL:
Home/CategoryProducts/category/c_50_ShowcasesDisplays
The action CategoryProducts on the HomeController will be called. Your category parameter value will be c_50_ShowcasesDisplays.
It will also build the corresponding URL when you use ActionLink, RouteLink, Url.Action, or UrlHelper.
#Html.ActionLink("ShowcasesDisplays", "CategoryProducts", "Home",
new { category = "c_50_ShowcasesDisplays" }, null)
// Generates URL /Home/CategoryProducts/category/c_50_ShowcasesDisplays
I'd like to handle URLs like this:
/Id/Key1/Value1/Key2/Value2/Key3/Value3/
Right now, I have set up a rule like this:
/{id}/{*parameters}
The parameters object is passed as a single string to all the actions that are involved in forming the response. This does work, but I have a few problems with it:
Each action must resolve the string for itself. I've, of course, made an extension method that turns the string to a Dictionary<string, string>, but I'd prefer it if the dispatching mechanism gave my methods a Dictionary<string, string> directly - or, better yet, the actual pairs as separate arguments.
Action links will still add parameters using the traditional format (?Key1=Value1). I guess I could write specialized helpers with my desired format, but I'd prefer it if there was a way to make the existing overloads follow the above routing rule.
Is there a way to do the above?
You could write a custom route:
public class MyRoute : Route
{
public MyRoute()
: base(
"{controller}/{action}/id/{*parameters}",
new MvcRouteHandler()
)
{
}
public override RouteData GetRouteData(HttpContextBase httpContext)
{
var rd = base.GetRouteData(httpContext);
if (rd == null)
{
return null;
}
string parameters = rd.GetRequiredString("parameters");
IDictionary<string, string> parsedParameters = YourExtensionMethodThatYouAlreadyWrote(parameters);
rd.Values["parameters"] = parsedParameters;
return rd;
}
public override VirtualPathData GetVirtualPath(RequestContext requestContext, RouteValueDictionary values)
{
object parameters;
if (values.TryGetValue("parameters", out parameters))
{
var routeParameters = parameters as IDictionary<string, object>;
if (routeParameters != null)
{
string result = string.Join(
"/",
routeParameters.Select(x => string.Concat(x.Key, "/", x.Value))
);
values["parameters"] = result;
}
}
return base.GetVirtualPath(requestContext, values);
}
}
which could be registered like that:
public static void RegisterRoutes(RouteCollection routes)
{
routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
routes.Add("my-route", new MyRoute());
routes.MapRoute(
"Default",
"{controller}/{action}/{id}",
new { controller = "Home", action = "Index", id = UrlParameter.Optional }
);
}
and now your controller actions could take the following parameters:
public ActionResult SomeAction(IDictionary<string, string> parameters)
{
...
}
As far as generating links following this pattern is concerned, it's as simple as:
#Html.RouteLink(
"Go",
"my-route",
new {
controller = "Foo",
action = "Bar",
parameters = new RouteValueDictionary(new {
key1 = "value1",
key2 = "value2",
})
}
)
or if you wanted a <form>:
#using (Html.BeginRouteForm("my-route", new { controller = "Foo", action = "Bar", parameters = new RouteValueDictionary(new { key1 = "value1", key2 = "value2" }) }))
{
...
}
Write your own model binder for a specialized dictionary. If you will have one there will be no need for parsing the string in each action method.
First of all it might be worth looking at this question:
How can I cache objects in ASP.NET MVC?
There some pseudo code that almost does what i want:
public class CacheExtensions
{
public static T GetOrStore<T>(this Cache cache, string key, Func<T> generator)
{
var result = cache[key];
if(result == null)
{
result = generator();
cache[key] = result;
}
return (T)result;
}
}
However, what I'd really like to do, is auto-generate the "key" from the generator. I figure i need to change the method signature to:
public static T GetOrStore<T>(this Cache cache,
System.Linq.Expressions.Expression<Func<T>> generator)
I want to use the method name, but also any parameters and their values to generate the key. I can get the method body from the expression, and the paramter names (sort of), but I have no idea how to get the paramter values...?
Or am I going about this the wrong way? Any ideas much appreciated.
Here's how I did it:
public static class ICacheExtensions
{
public static T GetOrAdd<T>(this ICache cache, Expression<Func<T>> getterExp)
{
var key = BuildCacheKey<T>(getterExp);
return cache.GetOrAdd(key, () => getterExp.Compile().Invoke());
}
private static string BuildCacheKey<T>(Expression<Func<T>> getterExp)
{
var body = getterExp.Body;
var methodCall = body as MethodCallExpression;
if (methodCall == null)
{
throw new NotSupportedException("The getterExp must be a MethodCallExpression");
}
var typeName = methodCall.Method.DeclaringType.FullName;
var methodName = methodCall.Method.Name;
var arguments = methodCall.Arguments
.Select(a => ExpressionHelper.Evaluate(a))
.ToArray();
return String.Format("{0}_{1}_{2}",
typeName,
methodName,
String.Join("|", arguments));
}
}
with this helper to evaluate nodes of an expression tree:
internal static class ExpressionHelper
{
public static object Evaluate(Expression e)
{
Type type = e.Type;
if (e.NodeType == ExpressionType.Convert)
{
var u = (UnaryExpression)e;
if (TypeHelper.GetNonNullableType(u.Operand.Type) == TypeHelper.GetNonNullableType(type))
{
e = ((UnaryExpression)e).Operand;
}
}
if (e.NodeType == ExpressionType.Constant)
{
if (e.Type == type)
{
return ((ConstantExpression)e).Value;
}
else if (TypeHelper.GetNonNullableType(e.Type) == TypeHelper.GetNonNullableType(type))
{
return ((ConstantExpression)e).Value;
}
}
var me = e as MemberExpression;
if (me != null)
{
var ce = me.Expression as ConstantExpression;
if (ce != null)
{
return me.Member.GetValue(ce.Value);
}
}
if (type.IsValueType)
{
e = Expression.Convert(e, typeof(object));
}
Expression<Func<object>> lambda = Expression.Lambda<Func<object>>(e);
Func<object> fn = lambda.Compile();
return fn();
}
}
When calling a function that produces a collection i want to cache i pass all my function's parameters and function name to the cache function which creates a key from it.
All my classes implement an interface that has and ID field so i can use it in my cache keys.
I'm sure there's a nicer way but somehow i gotta sleep at times too.
I also pass 1 or more keywords that i can use to invalidate related collections.