I use this extension to create an RouteUrl with the current query-string appended to it. This works fine for query-strings which does not have the same key twice or more.
public static string CurrentQueryStringRouteUrl(this UrlHelper url, string routeName, RouteValueDictionary routeValues)
{
var context = url.RequestContext;
var combinedRouteValues = new RouteValueDictionary();
var queryString = context.HttpContext.Request.QueryString;
foreach (var key in queryString.AllKeys.Where(key => key != null))
{
combinedRouteValues[key] = queryString[key];
}
if (routeValues != null)
{
foreach (var routeValue in routeValues)
{
combinedRouteValues[routeValue.Key] = routeValue.Value;
}
}
return url.RouteUrl(routeName, combinedRouteValues);
}
When there is query-string keys of same name, e.g. ?id=1&id=2&id=3, this is converted into ?id=1,2,3 using the method above. Is there any way to avoid that? I wish to keep the original query-string as I am binding these values on a model list.
I am aware I can create a custom model-binder to bind the comma separated string to string[] (or int[] in this example) but I wish to avoid this as far of consistence goes.
You can to do this, but it is dirty hack:
string _rawstring = context.HttpContext.Request.RawUrl;
int _f;
_f = _rawstring.IndexOf('?');
string _resultString = _rawstring.SubString(_f, _rawstring.Length);
Here you can find helpful info about that problem: How to deal with more than one value per key in ASP.NET MVC 3?
I took a slightly different approach, because I really needed separate duplicated keys in my query string. I alter the key using a counter, and then after rendering the url string, I restore the original parameter names. I needed this for GridMvc's grid_filter queries, but you can adapt it for your purpose.
/// <summary>
/// Allows you to create or extend a collection of route values to use in a url action
/// </summary>
public class RouteValueBuilder
{
readonly RouteValueDictionary routeValues;
private int gridFilterCounter = 0;
public RouteValueBuilder(object existingRouteValues = null)
{
routeValues = existingRouteValues as RouteValueDictionary ?? new RouteValueDictionary(existingRouteValues);
}
public void Add(string field, object value)
{
if (field == "grid_filter" && routeValues.ContainsKey(field))
{
// Because we can't add duplicate keys and GridMvc doesn't support joined comma format for query strings,
// we briefly rename each new filter, and then the Finalise method must be called after the url
// string is rendered to restore the grid_filter names back to normal.
gridFilterCounter++;
routeValues.Add(field + gridFilterCounter, value);
}
else if (routeValues.ContainsKey(field))
{
// Since duplicate key names are not supported, the concatenated comma approach can be used
routeValues[field] += "," + value;
}
else
{
routeValues.Add(field, value);
}
}
public RouteValueDictionary Get()
{
return routeValues;
}
/// <summary>
/// Cleans up the final string url, fixing workarounds done during the building process.
/// This must be called after the final url string is rendered.
/// </summary>
public static string Finalise(string url)
{
// Restores grid_filter parameters to their correct naming. See comments on Add method.
for (var i = 0; i < 100; i++)
{
url = url.Replace("grid_filter" + i, "grid_filter");
}
return url;
}
}
Usage:
var builder = new RouteValueBuilder();
builder.Add("grid_filter", "value1");
builder.Add("grid_filter", "value2");
string url = Html.Action("Index", "Home", builder.Get());
url = RouteValueBuilder.Finalise(url);
Edit: Note that the comma concatenation approach actually doesn't work in the class because it gets encoded, but the support for duplication is the main taker from this example.
Related
I am using c# and sitecore to basically use tokens in certain places ( see: how to create a custom token in sitecore ). I think I have a solution, but am not sure as to why it is not working, even though I am getting no errors.
Item tokenItem = Sitecore.Context.Database.Items["/sitecore/content/Site Content/Tokens"];
if (tokenItem.HasChildren)
{
var sValue = args.FieldValue.ToString();
foreach (Item child in tokenItem.Children)
{
if (child.Template.Name == "Token")
{
string home = child.Fields["Title"].Value;
string hContent = child.Fields["Content"].Value;
if (sValue.Contains(home))
{
home.Replace(home, hContent);
}
}
}
}
home and hContent pull up the correct values of each container, but when the page loads, it still has the "home" value inputted (the ie: ##sales) in the content area instead of the new value, which is stored in hContent. The sValue contains everything (tables, divs, text) and I was trying to single out a value that equals to "home" and replace the "home" value with hContent. What am I missing?
If your code is implemented as a processor for the RenderField pipeline, you need to put the result of your work back into args. Try something like this:
Item tokenItem = Sitecore.Context.Database.Items["/sitecore/content/Site Content/Tokens"];
if (tokenItem.HasChildren)
{
var sValue = args.Result.FirstPart;
foreach (Item child in tokenItem.Children){
if (child.Template.Name == "Token") {
string home = child.Fields["Title"].Value;
string hContent = child.Fields["Content"].Value;
if (sValue.Contains(home)) {
sValue = sValue.Replace(home, hContent);
}
}
}
args.Result.FirstPart = sValue;
}
Note that you need to be sure to patch this processor into the pipeline after the GetFieldValue processor. That processor is responsible for pulling the field value into args.Result.FirstPart.
You code isn't really doing anything. You seem to be replacing the tokens on the token item field itself (child.Fields["Title"] and child.Fields["Content"]), not on the output content stream.
Try the following, you need to set the args to the replaced value, replacing both the FirstPart and LastPart properties: Replace Tokens in Rich Text Fields Using the Sitecore ASP.NET CMS (link to the code in the "untested prototype" link).
I would refactor your code to make it easier:
public void Process(RenderFieldArgs args)
{
args.Result.FirstPart = this.Replace(args.Result.FirstPart);
args.Result.LastPart = this.Replace(args.Result.LastPart);
}
protected string Replace(string input)
{
Item tokenItem = Sitecore.Context.Database.Items["/sitecore/content/Site Content/Tokens"];
if (tokenItem.HasChildren)
{
foreach (Item child in tokenItem.Children)
{
if (child.Template.Name == "Token")
{
string home = child.Fields["Title"].Value;
string hContent = child.Fields["Content"].Value;
if (input.Contains(home))
{
return input.Replace(home, hContent);
}
}
}
}
return input;
}
This is still not optimal, but gets you closer.
Well, Do you know what happens when you performs home.Replace(home, hContent);, it will create a new instance by replacing the content of the come with what is in hContent so what you need to do is, assign this instance to a new variable or to home itself. hence the snippet will be like the following:
if (sValue.Contains(home))
{
home = home.Replace(home, hContent);
}
Have you tried:
home = home.Replace(home,hContent);
I cannot figure out how to fix this loop issue when i call a function like
new Common.Utility.Parameter().Get(Common.Constants.Parameter.SomeParameter);
Probably the error is caused by isHttpsCookie that recall the Parameter.Get()
Utility.cs
public static class Utility
{
public class Parameter
{
public string Get(string key)
{
string cookie = new Cookie().Read(key);
if (cookie == null)
{
var parameter = new Models.Parameter();
using (var db = new MyEntities())
parameter = db.Parameters.Where(w => w.Key == key).FirstOrDefault<Models.Parameter>();
if (parameter != null)
{
new Cookie().Write(key, parameter.Value);
return parameter.Value;
}
else
return string.Empty;
}
else
return cookie;
}
}
}
Cookie.cs
public class Cookie
{
private bool isHttpsCookie = Convert.ToBoolean(new Utility.Parameter().Get(Constants.Parameter.IsHttps)); // Loop here?
public string Read(string cookieName)
{
HttpCookie httpCookie = HttpContext.Current.Request.Cookies[HttpContext.Current.ApplicationInstance.GetType().BaseType.Assembly.GetName().Name + "_" + cookieName];
return httpCookie != null ? HttpContext.Current.Server.HtmlEncode(httpCookie.Value).Trim() : string.Empty;
}
public void Write(string cookieName, string cookieValue, bool isHttpCookie = true)
{
if (isHttpsCookie)
isHttpCookie = false;
var aCookie = new HttpCookie(HttpContext.Current.ApplicationInstance.GetType().BaseType.Assembly.GetName().Name + "_" + cookieName)
{Value = cookieValue, Expires = Common.Constants.Cookie.DefaultExpires, HttpOnly = isHttpCookie};
HttpContext.Current.Response.Cookies.Add(aCookie);
}
}
Apparently, your code is falling into a sort of recursion where you suspect it is. What I'm having trouble with is why are you creating new objects just to call a single method. Looks like you could have them as static methods in your classes, so no object creation would be needed, thus no 'looping'.
Have a closer look at your Cookie.Write() and Parameter.Get() method, they are calling each other. When you declare isHttpsCookie, you call Parameter.Get(). In the Parameter.Get(), if the condition is valid, it will call to Cookie.Write(). In its turn, when you call new Cookie(), the isHttpsCookie is called again and it's continuing forever.
Another point at this code:
if (isHttpsCookie)
isHttpCookie = false;
do you try to say that isHttpsCookie should be false at all time? so why do you need to declare this?
Solution: Do like #Takeshi said: those methods can be declared as static so no class declaration is required to called them.
You are correct in what you suspect. the isHttpsCookie declaration is causing you grief.
When the Cookie object is created it goes away and executes the method get from your utility class which creates an instance of cookie. Therefor you have your recursion.
You will need to change the way you initialise isHttpsCookie. Maybe only initialise / check if you are doing a write. After all you are most likely going to read more often than write.
Hope that helps.
I have a URL and from which i need to get names after "bussiness" and Before the Page Name i.e. "paradise-villas-little.aspx" from below URL.
http://test.com/anc/bussiness/accommo/resort/paradise-villas-little.aspx
I am not getting how can i get this. i have tried the RawUrl, but it fetched the full. Please help me how can i do this.
UPDATE: This is a type of URL, i need to check it for dynamically.
You can create a little helper, and parse the URL from it's Uri Segments :
public static class Helper
{
public static IEnumerable<String> ExtractSegments(this Uri uri, String exclusiveStart)
{
bool startFound = false;
foreach (var seg in uri.Segments.Select(i => i.Replace(#"/","")))
{
if (startFound == false)
{
if (seg == exclusiveStart)
startFound = true;
}
else
{
if (!seg.Contains("."))
yield return seg;
}
}
}
}
And call it like this :
Uri uri = new Uri(#"http://test.com/anc/bussiness/accommo/resort/paradise-villas-little.aspx");
var found = uri.ExtractSegments("bussiness").ToList();
Then found contains "accommo" and "resort", and this method is extensible to any URL length, with or without file name at the end.
Nothing sophisticated in this implementation, just regular string operations:
string url = "http://test.com/anc/bussiness/accommo/resort/paradise-villas-little.aspx";
string startAfter = "business";
string pageName = "paradise-villas-little.aspx";
char delimiter = '/'; //not platform specific
var from = url.IndexOf(startAfter) + startAfter.Length + 1;
var to = url.Length - from - pageName.Length - 1;
var strings = url.Substring(from, to).Split(delimiter);
You may want to add validations though.
You have to use built-in string methods. The best is to use String Split.
String url = "http://test.com/anc/bussiness/accommo/resort/paradise-villas-little.aspx";
String[] url_parts = url.Split('/'); //Now you have all the parts of the URL all folders and page. Access the folder names from string array.
Hope this helps
I am using Chello (the c# wrapper for the Trello API). I need to pass the argument "createCard" as per the documentation here: https://trello.com/docs/api/card/index.html
And this is the function I am using from Chello:
public IEnumerable<CardUpdateAction> ForCard(string cardId, object args)
{
string queryString = BuildQueryString(args);
return GetRequest<List<CardUpdateAction>>("/cards/{0}/actions?{1}", cardId, queryString);
}
I have tried calling this in this way:
List<CardUpdateAction> cua = chello.CardUpdates.ForCard("5264d37736695b2821001d7a","createCard").ToList();
but I get the error: Parameter Count Mismatch
on this function:
protected static string BuildQueryString(object args)
{
string queryString = String.Empty;
if (args != null)
{
StringBuilder sb = new StringBuilder();
foreach (var prop in args.GetType().GetProperties())
{
sb.AppendFormat("{0}={1}&", prop.Name, prop.GetValue(args, null));
}
if (sb.Length > 0) sb.Remove(sb.Length - 1, 1);
queryString = sb.ToString();
}
return queryString;
}
The problem is the fact that your API you are using expects you to pass in a class that has public properties equal to the tags you want to use.
This is very easy to do using Anonymous Types (I am doing a slightly different example to help illustrate a point)
//This will cause BuildQueryString to return "actions=createCard&action_fields=data,type,date"
var options = new { actions = "createCard", action_fields = "data,type,date" };
List<CardUpdateAction> cua = chello.CardUpdates.ForCard("5264d37736695b2821001d7a",options).ToList();
string is an object. Every type in .NET platform inherits from Object. This is called Unified Type System.
On the other hand, we have the Liskov Substitution Principle, which put simply, says that if B is a subtype of A (B is A), then you should be able to use B, wherever A is used.
Based on these reasons, you can pass string to any method that accepts an object as an argument.
You can test it:
public void DoSomething(object args)
{
}
public void Main()
{
DoSomething("some string argument, instead of the object");
}
It works just fine. No error.
I am building an online store for local artists, and one of the requirements is to add an image to be associated with a given product. For the image, there are multiple elements that need to be validated; specifically dimensions, file size, and type.
Currently, I have the following set up to validate the image:
[LocalizedDisplayName(typeof(StoreManagementRes), "Image")]
[ImageSize(typeof(BesLogicSharedRes),"ValidationImageFileSizeMustBeLessThan20kb")]
[ImageDimension(typeof(BesLogicSharedRes), "ValidationImageDimensionMustBeLessThan640x480")]
[ImageType(typeof(BesLogicSharedRes), "ValidationImageTypeMustBeJpgOrPng")]
public int ImageFileId { get; set; }
The file that is uploaded does get validated properly, however, they are not necessarily called in the same order every time the application runs. In the end, if validation fails on more than one attribute, only one error message gets displayed. Again, not necessarily the first failed validation, nor the last. I would like to display all the errors at once so as not to frustrate the user.
If this is relevant, all three image validation classes are sub classed from ValidationAttribute.
One thing to be thankful of is that the model keeps all errors rather than one of them, it's just the HtmlHelper that's displaying the first.
ValidationSummary should in fact display all errors on your model though I suspect you want the equivalent for an individual property.
Unfortunately a couple of the useful methods are private rather than protected so they had to be copy and pasted out of ValidationExtensions.cs. I did this with a slightly cut down version (no use of resource files for error messages, easy enough to do by getting the original version of GetUserErrorMessageOrDefault but you'll also have to check to take the related methods and fields from the class too). I also only did one function call but it's easy enough to impliment the overloads if needed.
public static MvcHtmlString ValidationSummaryForSubModel(this HtmlHelper html, bool excludePropertyErrors, string message, IDictionary<string, object> htmlAttributes)
{
string prefix = html.ViewData.TemplateInfo.HtmlFieldPrefix;
var props = html.ViewData.ModelState.Where(x => x.Key.StartsWith(prefix));
var errorprops = props.Where(x => x.Value.Errors.Any()).SelectMany(x=>x.Value.Errors);
if (html == null) {
throw new ArgumentNullException("html");
}
FormContext formContext = (html.ViewContext.ClientValidationEnabled) ? html.ViewContext.FormContext : null;
if (formContext == null && html.ValidForSubModel())
{
return null;
}
string messageSpan;
if (!String.IsNullOrEmpty(message)) {
TagBuilder spanTag = new TagBuilder("span");
spanTag.SetInnerText(message);
messageSpan = spanTag.ToString(TagRenderMode.Normal) + Environment.NewLine;
}
else {
messageSpan = null;
}
StringBuilder htmlSummary = new StringBuilder();
TagBuilder unorderedList = new TagBuilder("ul");
foreach (ModelError modelError in errorprops) {
string errorText = GetUserErrorMessageOrDefault(html.ViewContext.HttpContext, modelError, null /* modelState */);
if (!String.IsNullOrEmpty(errorText)) {
TagBuilder listItem = new TagBuilder("li");
listItem.SetInnerText(errorText);
htmlSummary.AppendLine(listItem.ToString(TagRenderMode.Normal));
}
}
if (htmlSummary.Length == 0) {
htmlSummary.AppendLine(_hiddenListItem);
}
unorderedList.InnerHtml = htmlSummary.ToString();
TagBuilder divBuilder = new TagBuilder("div");
divBuilder.MergeAttributes(htmlAttributes);
divBuilder.AddCssClass((html.ViewData.ModelState.IsValid) ? HtmlHelper.ValidationSummaryValidCssClassName : HtmlHelper.ValidationSummaryCssClassName);
divBuilder.InnerHtml = messageSpan + unorderedList.ToString(TagRenderMode.Normal);
if (formContext != null) {
// client val summaries need an ID
divBuilder.GenerateId("validationSummary");
formContext.ValidationSummaryId = divBuilder.Attributes["id"];
formContext.ReplaceValidationSummary = !excludePropertyErrors;
}
return MvcHtmlString.Create(divBuilder.ToString(TagRenderMode.Normal));
}
private static string GetUserErrorMessageOrDefault(HttpContextBase httpContext, ModelError error, ModelState modelState)
{
if (!String.IsNullOrEmpty(error.ErrorMessage))
{
return error.ErrorMessage;
}
if (modelState == null)
{
return null;
}
string attemptedValue = (modelState.Value != null) ? modelState.Value.AttemptedValue : null;
//return String.Format(CultureInfo.CurrentCulture, GetInvalidPropertyValueResource(httpContext), attemptedValue);
return "Error";
}