replace DescriptionAttr with Dictionary or smth - asp.net

I have a enum that's contain Description Attribute(for audit):
public enum ActivityType
{
[NotExist("Not Assign")]
[Description("Change Level")]
LevelChanged,
[NotExist("Not Assign")]
[Description("Change Skill Level")]
SkillLevelChanged
}
And all had been great until i needed to put Desription in Resource files(attribute didn't support them), so i need Dictionary or something like this.The question is: How implement this feature without big changing in all another logic?Something like this:
private static readonly Dictionary<ActivityType, String> ActivityDescription = new Dictionary<ActivityType, String>()
{
{ActivityType.LevelChanged, "Change"},
{ActivityType.SkillLevelChanged, "SkillChange"}
}

The solution that requires the minimal amount of code change for you would be to alter your descriptions to be the resource keys in your resource file. Then you could read these dynamically by doing something like:
[Description("Change_Level")]
Then your resource key/value would be:
Change_Level Change Level
And to read it you can do:
FieldInfo fi = value.GetType().GetField(value.ToString());
DescriptionAttribute attribute = value.GetType()
.GetField(value.ToString())
.GetCustomAttributes(typeof(DescriptionAttribute), false)
.SingleOrDefault() as DescriptionAttribute;
if (attribute != null)
{
var resManager = new ResourceManager(typeof(MyResources));
return resManager.GetString(attribute.Description);
}
else
{
return value.ToString();
}
If however you're wanting a nicer solution with the option to pass in the resource file, you can hijack the Display attribute:
[Display(ResourceType = typeof(MyResources), Name = "Change_Level")]
Then you can do:
FieldInfo fi = value.GetType().GetField(value.ToString());
DisplayAttribute attribute = value.GetType()
.GetField(value.ToString())
.GetCustomAttributes(typeof(DisplayAttribute), false)
.SingleOrDefault() as DisplayAttribute;
if (attribute != null)
{
var resManager = new ResourceManager(attribute.ResourceType);
return resManager.GetString(attribute.Name);
}
else
{
return value.ToString();
}

Related

How do you make a class method modify itself?

asp.net C#4
I have a simple class to working with query strings.
A new instance is created like this:
public QueryString(string querystring)
{
try
{
_table = new Hashtable();
if (querystring.Length > 0)
{
foreach (string pair in querystring.Split('&'))
{
string[] item = pair.Split('=');
_table.Add(item[0].ToLower(), item[1]);
}
}
}
catch (Exception)
{
}
}
I want to add a method to this that will remove a key value pair. I don't want it to return a new querystring, I just want it to remove the pair from the current instance. Not sure how to do that since it says I can't assign a value to 'this'
public void Remove(string key)
{
String querystring = this.ToString();
try
{
_table = new Hashtable();
if (key.Length > 0)
{
foreach (string pair in querystring.Split('&'))
{
string[] item = pair.Split('=');
if (item[0] != key)
{
_table.Add(item[0].ToLower(), item[1]);
}
}
this = _table;
}
}
catch (Exception)
{
}
}
You're overcomplicating things. Since your class's state is made up of the _table field, all you need to do is remove the item with the given key from that field.
The following example replaces your untyped Hashtable wit a strongly-typed Dictionary. I also chose to initialize the dictionary with a LINQ statement, but you could keep your old code there if you prefer.
public class QueryString
{
private readonly Dictionary<string, string> _table;
public QueryString(string querystring)
{
if (querystring.Length > 0)
{
var pairs =
from pair in querystring.Split('&')
let item = pair.Split('=')
select new {key = item[0], value = item[1]};
_table = pairs.ToDictionary(p => p.key, p => p.value);
}
}
public void Remove(string key)
{
_table.Remove(key);
}
}
You cannot assign a value to this since it is a reference to the object itself.
However, if you remove the line this = _table; , isn't things working as they should then? I guess your ToString() is somewhat using the hashtable to generate a "printer friendly" QueryString, and if that is the case, the way I see it, your Remove() method should be working (since you are replacing the _table variable with a new HashTable not including the key-value pair you want to exclude).
you are passing a querystring into the class so the original querystring IS intact.
However you then break down the querystring into a a Hashtable of key/value pairs. If you want to keep THAT intact you need to clone the HashTable and perform the remove on the clone.
In any case it's probably a good idea to keep the querystring you are passing in as a constructor parameter in a member variable for safe keeping.

Retrieve Arguments of a Workflow (with default values)?

Given is a Workflow Foundation 4 runtime that is working against a website ;)
We need to get the arguments of workflows to show the user an editor to enter the arguments. For that we need all arguments with names, types and - default values, as well as an indication whether an argument is required.
Workflows are stored as XAML files.
How to do that? The data seems to be in the Activity Metadata which seems to be not avaialble outside the Workflow. In addition, the Workflow Engine ModelService is for the Designer and has a lot of overhead.
Any easy way to retrieve this information?
I've already done something similar. Reflection might be your best (and only) option if you want a generic approach.
// Just an holder for InArgument informations
class InArgumentInfo
{
public string InArgumentName { get; set; }
public string InArgumentDescription { get; set; }
public bool InArgumentIsRequired { get; set; }
}
static ICollection<InArgumentInfo> GetInArgumentsInfos(Activity activity)
{
var properties = activity.GetType()
.GetProperties()
.Where(p => typeof(InArgument).IsAssignableFrom(p.PropertyType))
.ToList();
var argumentsCollection = new Collection<InArgumentInfo>();
foreach (var property in properties)
{
var descAttribute = property
.GetCustomAttributes(false)
.OfType<DescriptionAttribute>()
.FirstOrDefault();
string description = descAttribute != null && !string.IsNullOrEmpty(descAttribute.Description) ?
descAttribute.Description :
string.Empty;
bool isRequired = property
.GetCustomAttributes(false)
.OfType<RequiredArgumentAttribute>()
.Any();
argumentsCollection.Add(new InArgumentInfo
{
InArgumentName = property.Name,
InArgumentDescription = description,
InArgumentIsRequired = isRequired
});
}
return argumentsCollection;
}
This way you can not only retrieve the argument's name but also other information hold by the argument's attributes. For example I choose to give argument an user-friendly name through [Description] attribute (eg. instead of MyPropertyName user sees "My Property Name").
Note: if you can ensure that you activity is an ActivityBuilder or DynamicActivity they both have Properties property that you can use, but the principle is the same.
Load it as DynamicActivity and iterate over Properties property
var dynamicActivity = ActivityXamlServices.Load(foo) as DynamicActivity
foreach(DynamicActivityProperty prop in dynamicActivity.Properties)
{
// ...
}
UPDATE: Missed default value part
foreach (var prop in dynamicActivity .Properties)
{
object defaultValue;
if (prop.Value == null)
{
defaultValue = null;
}
else
{
Type genericTypeDefinition = prop.Type.GetGenericTypeDefinition();
if (genericTypeDefinition == typeof(InArgument<>) || genericTypeDefinition == typeof(InOutArgument<>))
{
var valueProp = prop.Value.GetType().GetProperty("Expression", System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.DeclaredOnly);
var expression = valueProp.GetValue(prop.Value, null);
var expressionValueProp = expression.GetType().GetProperty("Value");
defaultValue = expressionValueProp.GetValue(expression, null);
}
}
}
Not totally guaranteed, there are some checks you have to do.

default values in LINQ query

I have following Linq query/function in my MVC3 application.
public AuditTrail GetNamesAddressesEmployers(long registryId , int changedField) {
var otherNameAndAddress = (from a in context.AuditTrails
where a.ChangedField == changedField
&& a.RegistryId == registryId
select a).FirstOrDefault();
return otherNameAndAddress;
}
I want that if otherNameAndAddress = null then its properties should be assigned some values.
otherNameAndAddress has Name and description property. This GetNamesAddressesEmployers is being used at 3 places. I want to assign different values to name and description when otherNameAndAddress = null at all three locations.
You're already using FirstOrDefault() so why not specify the Default:
public AuditTrail GetNamesAddressesEmployers(long registryId, int changedField)
{
return context.AuditTrails
.Where(a => a.ChangedField == changedField
&& a.RegistryId == registryId)
.DefaultIfEmpty(new AuditTrail { /* fill properties here */ })
.FirstOrDefault();
}
Well, you could change the return statement to:
return otherNameAndAddress ?? new AuditTrail { Name = "Default",
Description = "Default };
or something like that... but you say you want to assign different default values for different calls. That means you'll either need to pass the default in, or perform the defaulting (e.g. in the same way, via the null-coalescing operator) at the call site.
For example:
public AuditTrail GetNamesAddressesEmployers(long registryId, int changedField,
AuditField defaultValue) {
var otherNameAndAddress = (from a in context.AuditTrails
where a.ChangedField == changedField
&& a.RegistryId == registryId
select a).FirstOrDefault();
return otherNameAndAddress && defaultValue;
}
or keep it as it currently is, and use this at the call site:
var auditTrail = GetNamesAddressesEmployers(registryId, changedField) ??
new AuditTrail { Name = "Foo", Description = "Bar" };
It's not really clear which is best based on your description.
EDIT: As mentioned by Justin, you could use DefaultIfEmpty instead (just before FirstOrDefault). That means you have to pass the value in rather than doing it at the call site, but other than that they're very similar solutions.

ASP.NET: How to check the value of a cached dictionary?

Not really sure how to do this but i can cache the dictionary like this:
Cache.Insert("CacheName", Dictionary)
need some direction. the dictionary is two string values taken from a database. The user will input a string and i need to compare it against the values in the cached dictionary.
In general you need to access the object from the cache, cast it, and the use the ContainsKey property. Here is an example:
First add the dictionary to the Cache:
IDictionary<string, string> testDict = new Dictionary<string, string>();
testDict.Add("Test", "test");
Cache.Insert("dict", testDict);
Then, when you need to do so, access the cached object and use it ContainsKey property to determine whether it contains the searched key or not.
var dict = Cache["dict"] as IDictionary<string, string>;
if (dict != null)
{
string testValue = "test";
if(dict.ContainsKey(testValue))
{
/* some logic here */
}
}
You can access the value the following way:
if (dict != null)
{
string testValue = "test";
if(dict.ContainsKey(testValue))
{
/* some logic here */
string value = dict[testValue];
}
}
You can get the dictionary out of the cache by writing
var dict = (Dictionary<X, Y>) cache["CacheName"];

NameValueCollection to URL Query?

I know i can do this
var nv = HttpUtility.ParseQueryString(req.RawUrl);
But is there a way to convert this back to a url?
var newUrl = HttpUtility.Something("/page", nv);
Simply calling ToString() on the NameValueCollection will return the name value pairs in a name1=value1&name2=value2 querystring ready format. Note that NameValueCollection types don't actually support this and it's misleading to suggest this, but the behavior works here due to the internal type that's actually returned, as explained below.
Thanks to #mjwills for pointing out that the HttpUtility.ParseQueryString method actually returns an internal HttpValueCollection object rather than a regular NameValueCollection (despite the documentation specifying NameValueCollection). The HttpValueCollection automatically encodes the querystring when using ToString(), so there's no need to write a routine that loops through the collection and uses the UrlEncode method. The desired result is already returned.
With the result in hand, you can then append it to the URL and redirect:
var nameValues = HttpUtility.ParseQueryString(Request.QueryString.ToString());
string url = Request.Url.AbsolutePath + "?" + nameValues.ToString();
Response.Redirect(url);
Currently the only way to use a HttpValueCollection is by using the ParseQueryString method shown above (other than reflection, of course). It looks like this won't change since the Connect issue requesting this class be made public has been closed with a status of "won't fix."
As an aside, you can call the Add, Set, and Remove methods on nameValues to modify any of the querystring items before appending it. If you're interested in that see my response to another question.
string q = String.Join("&",
nvc.AllKeys.Select(a => a + "=" + HttpUtility.UrlEncode(nvc[a])));
Make an extension method that uses a couple of loops. I prefer this solution because it's readable (no linq), doesn't require System.Web.HttpUtility, and it supports duplicate keys.
public static string ToQueryString(this NameValueCollection nvc)
{
if (nvc == null) return string.Empty;
StringBuilder sb = new StringBuilder();
foreach (string key in nvc.Keys)
{
if (string.IsNullOrWhiteSpace(key)) continue;
string[] values = nvc.GetValues(key);
if (values == null) continue;
foreach (string value in values)
{
sb.Append(sb.Length == 0 ? "?" : "&");
sb.AppendFormat("{0}={1}", Uri.EscapeDataString(key), Uri.EscapeDataString(value));
}
}
return sb.ToString();
}
Example
var queryParams = new NameValueCollection()
{
{ "order_id", "0000" },
{ "item_id", "1111" },
{ "item_id", "2222" },
{ null, "skip entry with null key" },
{ "needs escaping", "special chars ? = &" },
{ "skip entry with null value", null }
};
Console.WriteLine(queryParams.ToQueryString());
Output
?order_id=0000&item_id=1111&item_id=2222&needs%20escaping=special%20chars%20%3F%20%3D%20%26
This should work without too much code:
NameValueCollection nameValues = HttpUtility.ParseQueryString(String.Empty);
nameValues.Add(Request.QueryString);
// modify nameValues if desired
var newUrl = "/page?" + nameValues;
The idea is to use HttpUtility.ParseQueryString to generate an empty collection of type HttpValueCollection. This class is a subclass of NameValueCollection that is marked as internal so that your code cannot easily create an instance of it.
The nice thing about HttpValueCollection is that the ToString method takes care of the encoding for you. By leveraging the NameValueCollection.Add(NameValueCollection) method, you can add the existing query string parameters to your newly created object without having to first convert the Request.QueryString collection into a url-encoded string, then parsing it back into a collection.
This technique can be exposed as an extension method as well:
public static string ToQueryString(this NameValueCollection nameValueCollection)
{
NameValueCollection httpValueCollection = HttpUtility.ParseQueryString(String.Empty);
httpValueCollection.Add(nameValueCollection);
return httpValueCollection.ToString();
}
Actually, you should encode the key too, not just value.
string q = String.Join("&",
nvc.AllKeys.Select(a => $"{HttpUtility.UrlEncode(a)}={HttpUtility.UrlEncode(nvc[a])}"));
Because a NameValueCollection can have multiple values for the same key, if you are concerned with the format of the querystring (since it will be returned as comma-separated values rather than "array notation") you may consider the following.
Example
var nvc = new NameValueCollection();
nvc.Add("key1", "val1");
nvc.Add("key2", "val2");
nvc.Add("empty", null);
nvc.Add("key2", "val2b");
Turn into: key1=val1&key2[]=val2&empty&key2[]=val2b rather than key1=val1&key2=val2,val2b&empty.
Code
string qs = string.Join("&",
// "loop" the keys
nvc.AllKeys.SelectMany(k => {
// "loop" the values
var values = nvc.GetValues(k);
if(values == null) return new[]{ k };
return nvc.GetValues(k).Select( (v,i) =>
// 'gracefully' handle formatting
// when there's 1 or more values
string.Format(
values.Length > 1
// pick your array format: k[i]=v or k[]=v, etc
? "{0}[]={1}"
: "{0}={1}"
, k, HttpUtility.UrlEncode(v), i)
);
})
);
or if you don't like Linq so much...
string qs = nvc.ToQueryString(); // using...
public static class UrlExtensions {
public static string ToQueryString(this NameValueCollection nvc) {
return string.Join("&", nvc.GetUrlList());
}
public static IEnumerable<string> GetUrlList(this NameValueCollection nvc) {
foreach(var k in nvc.AllKeys) {
var values = nvc.GetValues(k);
if(values == null) { yield return k; continue; }
for(int i = 0; i < values.Length; i++) {
yield return
// 'gracefully' handle formatting
// when there's 1 or more values
string.Format(
values.Length > 1
// pick your array format: k[i]=v or k[]=v, etc
? "{0}[]={1}"
: "{0}={1}"
, k, HttpUtility.UrlEncode(values[i]), i);
}
}
}
}
As has been pointed out in comments already, with the exception of this answer most of the other answers address the scenario (Request.QueryString is an HttpValueCollection, "not" a NameValueCollection) rather than the literal question.
Update: addressed null value issue from comment.
The short answer is to use .ToString() on the NameValueCollection and combine it with the original url.
However, I'd like to point out a few things:
You cant use HttpUtility.ParseQueryString on Request.RawUrl. The ParseQueryString() method is looking for a value like this: ?var=value&var2=value2.
If you want to get a NameValueCollection of the QueryString parameters just use Request.QueryString().
var nv = Request.QueryString;
To rebuild the URL just use nv.ToString().
string url = String.Format("{0}?{1}", Request.Path, nv.ToString());
If you are trying to parse a url string instead of using the Request object use Uri and the HttpUtility.ParseQueryString method.
Uri uri = new Uri("<THE URL>");
var nv = HttpUtility.ParseQueryString(uri.Query);
string url = String.Format("{0}?{1}", uri.AbsolutePath, nv.ToString());
I always use UriBuilder to convert an url with a querystring back to a valid and properly encoded url.
var url = "http://my-link.com?foo=bar";
var uriBuilder = new UriBuilder(url);
var query = HttpUtility.ParseQueryString(uriBuilder.Query);
query.Add("yep", "foo&bar");
uriBuilder.Query = query.ToString();
var result = uriBuilder.ToString();
// http://my-link.com:80/?foo=bar&yep=foo%26bar
In AspNet Core 2.0 you can use QueryHelpers AddQueryString method.
As #Atchitutchuk suggested, you can use QueryHelpers.AddQueryString in ASP.NET Core:
public string FormatParameters(NameValueCollection parameters)
{
var queryString = "";
foreach (var key in parameters.AllKeys)
{
foreach (var value in parameters.GetValues(key))
{
queryString = QueryHelpers.AddQueryString(queryString, key, value);
}
};
return queryString.TrimStart('?');
}
This did the trick for me:
public ActionResult SetLanguage(string language = "fr_FR")
{
Request.UrlReferrer.TryReadQueryAs(out RouteValueDictionary parameters);
parameters["language"] = language;
return RedirectToAction("Index", parameters);
}
You can use.
var ur = new Uri("/page",UriKind.Relative);
if this nv is of type string you can append to the uri first parameter.
Like
var ur2 = new Uri("/page?"+nv.ToString(),UriKind.Relative);

Resources