Proper way of passing additional view data to EditorFor - c#

If I pass view data like this
#(Html.EditorFor(x => x.User2, "GenericList",
new ViewDataDictionary {
{ "ListType", ListType.CustomValidation },
{ "Param1", ObjectType.Asset },
{ "Param2", "User2" },
{ "DefaultValue", Model.User2 }
}
))
I am unable to access the Values via ViewData["ListType"]. The keys are stored in a property called Keys, and the values are stored in a ICollection property called Values
Passing the ViewData this way
#Html.EditorFor(x => x.User2, "GenericList",
new { ListType = ListType.CustomValidation,
Param1 = ObjectType.Asset,
Param2 = "User2",
DefaultValue = Model.User2
}
)
means I can get the values easily like ViewData["ListType"].
I don't liek the 2nd method of doing things because it's using anonymous types instead of a ViewDataDictionary. How can I use the first method but still call the ViewData as I expect to be,

Related

Replace property values in a class from List<Dictionary> values

I have a method that takes a List<Dictionary<string,object>> as a parameter. The plan is to use that parameter, but only update the values held in a particular class. Here is the (partially written) method
public async Task<Errors> UpdatePageForProject(Guid projectId, List<Dictionary<string, object>> data)
{
if (!IsValidUserIdForProject(projectId))
return new Errors { ErrorMessage = "Project does not exist", Success = false };
if (data.Count == 0)
return new Errors { ErrorMessage = "No data passed to change", Success = false };
var page = await _context.FlowPages.FirstOrDefaultAsync(t => t.ProjectId == projectId);
foreach (var d in data)
{
}
return new Errors { Success = true };
}
My original plan is to take each dictionary, check if the key and the property in page match and then alter the value (so I can pass in 1 dictionary or 8 dictionaries in the list and then alter page to save back to my entity database).
I'd rather not use reflection due to the speed hit (though C#9 is really fast, I'd still rather not use it), but I'm not sure how else this can be done. I did consider using AutoMapper to do this, but for now would rather not (it's a PoC, so it is possibly overkill)
If you want to do this without Reflection (which I agree is a good idea, not just for performance reasons) then you could use a "map" or lookup table with actions for each property.
var map = new Dictionary<string,Action<Page,object>>()
{
{ "Title", (p,o) => p.Title = (string)o },
{ "Header", (p,o) => p.Field1 = (string)o },
{ "DOB", (p,o) => p.DateOfBirth = (DateTime)o }
};
You can then iterate over your list of dictionaries and use the map to execute actions that update the page.
foreach (var dictionary in data)
{
foreach (entry in dictionary)
{
var action = map[entry.Key];
action(page, entry.Value);
}
}

Passing collection data to K2 custom service broker method

I have two K2 SmartObjects: SmartObjectA gets a list of objects. SmartObjectB is a custom SmartObject that executes a custom create method with the list returned from SmartObjectA (basic smart object) as input parameter.
In the custom service broker's code behind, I need to deserialize the list value.
How can I pass the list received in K2 as an input parameter for the create method? I am using a multivalue property and assigning SmartObjectA properties to it.
What format does K2 use to serialize multivalues and how can I check what the input value format is so that I can map the list correctly?
SmartObject A Properties:
Name - SOType.Text,
ID - SOType.Number,
Date - SOType.Date,
Description - SOType.Text
SmartObject B - Property to be populated with SmartObjectA's list:
[Property("LineItems", SoType.MultiValue, "LineItems", "The line items.")]
public string LineItems
{
get
{
return _lineItems;
}
set { _lineItems = value; }
}
Input properties and Create method for SmartObjectB:
[SourceCode.SmartObjects.Services.ServiceSDK.Attributes.Method("Create",
MethodType.Create,
"Create",
"Description",
new string[] { "CustomerId", "Date", "DueDate" },
new string[] { "CustomerId", "Date", "DueDate", "LineItems" },
new string[] { "TaxInvoiceId" })]
public TaxInvoice Create()
{
try
{
..deserialize here
}
}
Since you already have 2 different smartobjects, why not consider building a composite smartobject.
Using a composite smartobject, effectively doesnt require you to build a custom service broker.
Composite Smartobject Creation

Unable to pass a native value in ActionLink under Razor while POCO instances work great

First I thought that I'm using the wrong overload again (a very common gotcha in the API - everybody trips over that one). But wouldn't you know? That's not it. I actually had the HTML attributes parameter too and I verified with intellisense that it's the route values I'm entering.
#Html.ActionLink("Poof", "Action", "Home", 10, new { #class = "nav-link" })
Nevertheless, it seem that the receiving method below only sees null and crashes as it can't make an integer out of it.
public ActionResult Record(int count) { ... }
I've tried a few things: changed parameter type to int? and string (the program stops crashing but the value is still null). I've tested to package the passed value as an object (with/without #).
#Html.ActionLink("Poof", "Record", "Home",
new { count = "bamse" },
new { #class = "nav-link" })
I can see that the anchor produced has my value as a query string, so the changes are there. However, I still get null only in the method.
What am I missing?
The weird thing is that the following works fine.
#Html.ActionLink("Poof", "Record", "Home",
new Thing(),
new { #class = "nav-link" })
public ActionResult Record(Thing count) { ... }
Your using the overload of #Html.ActionLink() that expects the 4th parameter to be typeof object. Internally the method builds a RouteValueDictionary by using the .ToString() value of each property in the object.
In your case your 'object' (an int) has no properties, so no route values are generated and the url will be just /Home/Action(and you program crashes because your method expects a non null parameter).
If for example you changed it to
#Html.ActionLink("Poof", "Action", "Home", "10", new { #class = "nav-link" })
i.e. quoting the 4th parameter, the url would now be /Home/Action?length=2 because typeof string has a property length and there a 2 characters in the value.
In order to pass a native value you need to use the format
#Html.ActionLink("Poof", "Action", "Home", new { count = 10 }, new { #class = "nav-link" })
Which will generate /Home/Action?count=10 (or /Home/Action/10 if you create a specific route definition with Home/Action/{count})
Note also that passing a POCO in your case only works correctly because your POCO contains only value type properties. If for example, it also contained a property which was (say) public List<int> Numbers { get; set; } then the url created would include ?Numbers=System.Collections.Generic.List[int] (and binding would fail) so be careful passing complex objects in an action link
Hard to say what might be wrong with your code from the information provided in your question but assuming total defaults (a newly created ASP.NET MVC application in Visual Studio), if you add the following markup in your ~/Views/Home/Index.cshtml:
Html.ActionLink(
"Poof",
"Record",
"Home",
new { count = "bamse" },
new { #class = "nav-link" }
)
and the following action in your HomeController:
public ActionResult Record(string count)
{
return Content(count);
}
upon clicking on the generated anchor, the correct action will be invoked and the correct parameter passed to it.
The generated markup will look like this:
<a class="nav-link" href="/Home/Record?count=bamse">Poof</a>
So I guess that now the question that you should be asking yourself is: how does my setup differs with what Darin has outlined here? Answering this question might hold the key to your problem.
UPDATE:
OK, now you seem to have changed your question. You seem to be trying to pass complex objects to your controller action:
public ActionResult Record(Thing count) { ... }
Of course this doesn't work as you might expect. So make sure that you pass every single property that you want to be available when constructing your anchor:
Html.ActionLink(
"Poof",
"Record",
"Home",
new { ThingProp1 = "prop1", ThingProp2 = "prop2" },
new { #class = "nav-link" }
)
Alternatively, and of course a much better approach to handle this situation is to attribute an unique identifier to your models, so that all its needed in order to retrieve this model from your backend is this identifier:
Html.ActionLink(
"Poof",
"Record",
"Home",
new { id = "123" },
new { #class = "nav-link" }
)
and then in your controller action simply use this identifier to retrieve your Thing:
public ActionResult Record(int id)
{
Thing model = ... fetch the Thing using its identifier
}

Map enum value robustly

I have a form where I collect data from users. When this data is collected, I pass it to various partners, however each partner has their own rules for each piece of data, so this has to be converted. I can make this happen, but my worries are about the robustness. Here's some code:
First, I have an enum. This is mapped to dropdown a dropdown list - the description is the text value, and the int mapped to the value.
public enum EmploymentStatusType
{
[Description("INVALID!")]
None = 0,
[Description("Permanent full-time")]
FullTime = 1,
[Description("Permanent part-time")]
PartTime = 2,
[Description("Self employed")]
SelfEmployed = 3
}
When the form is submitted, the selected value is converted to its proper type and stored in another class - the property looks like this:
protected virtual EmploymentStatusType EmploymentStatus
{
get { return _application.EmploymentStatus; }
}
For the final bit of the jigsaw, I convert the value to the partners required string value:
Dictionary<EmploymentStatusType, string> _employmentStatusTypes;
Dictionary<EmploymentStatusType, string> EmploymentStatusTypes
{
get
{
if (_employmentStatusTypes.IsNull())
{
_employmentStatusTypes = new Dictionary<EmploymentStatusType, string>()
{
{ EmploymentStatusType.FullTime, "Full Time" },
{ EmploymentStatusType.PartTime, "Part Time" },
{ EmploymentStatusType.SelfEmployed, "Self Employed" }
};
}
return _employmentStatusTypes;
}
}
string PartnerEmploymentStatus
{
get { return _employmentStatusTypes.GetValue(EmploymentStatus); }
}
I call PartnerEmploymentStatus, which then returns the final output string.
Any ideas how this can be made more robust?
Then you need to refactor it into one translation area. Could be something like a visitor pattern implementation. Your choices are distribute the code (as you are doing now) or visitor which would centralize it. You need to build in a degree of fragility so your covering tests will show problems when you extend in order to force you to maintain the code properly. You are in a fairly common quandry which is really a code organisational one
I did encounter such a problem in one of my projects and I solved it by using a helper function and conventions for resource names.
The function is this one:
public static Dictionary<T, string> GetEnumNamesFromResources<T>(ResourceManager resourceManager, params T[] excludedItems)
{
Contract.Requires(resourceManager != null, "resourceManager is null.");
var dictionary =
resourceManager.GetResourceSet(culture: CultureInfo.CurrentUICulture, createIfNotExists: true, tryParents: true)
.Cast<DictionaryEntry>()
.Join(Enum.GetValues(typeof(T)).Cast<T>().Except(excludedItems),
de => de.Key.ToString(),
v => v.ToString(),
(de, v) => new
{
DictionaryEntry = de,
EnumValue = v
})
.OrderBy(x => x.EnumValue)
.ToDictionary(x => x.EnumValue, x => x.DictionaryEntry.Value.ToString());
return dictionary;
}
The convention is that in my resource file I will have properties that are the same as enum values (in your case None, PartTime etc). This is needed to perform the Join in the helper function which, you can adjust to match your needs.
So, whenever I want a (localized) string description of an enum value I just call:
var dictionary = EnumUtils.GetEnumNamesFromResources<EmploymentStatusType>(ResourceFile.ResourceManager);
var value = dictionary[EmploymentStatusType.Full];

Why won't my drop down default to the given value?

I've created a SelectList from a enum. The enum has a description, and the int value is being set to the value I want to store in the database.
The problem is, the default (BLANK) I set on construction isn't being used.
This is my enum:
public enum Stage
{
[Description("")]
BLANK = -99,
[Description("No Data")]
NoData = 9999,
[Description("Nil")]
Nil = 0,
[Description("Action")]
SAction = 1,
[Description("Action Plus")]
SActionPlus = 2,
[Description("Full")]
Full = 3
}
I create it in my controller:
private static IEnumerable<SelectListItem> GenerateSenStageList()
{
var values = from Stage e in Enum.GetValues(typeof(Stage))
select new { ID = (int)e, Name = e.ToDescription() };
return new SelectList(values, "Id", "Name", (int)Stage.BLANK);
}
Where I thought the final parameter set the selected item.
I assign it as ViewData, and access it like:
<%= Html.DropDownList("Stage", (IEnumerable<SelectListItem>)ViewData["StageList"])%>
However, Nil is always the selected value.
What am I missing here??
Thanks!
The last parameter does determine the selected value. However, you are passing a Stage enumerated value as the last parameter, while the actual elements of your list are made up of an ID value and a Stage value. To make this work, you have to pass it the actual object from values with a Stage value of BLANK.
Iainie,
Using your code, i managed to get this working first time. here's my amended code (using the accountcontroller for testing) [using .net 3.5]:
// from account controller - put the enum, etc in there for brevity
public enum Stage
{
[Description("")]
BLANK = -99,
[Description("No Data")]
NoData = 9999,
[Description("Nil")]
Nil = 0,
[Description("Action")]
SAction = 1,
[Description("Action Plus")]
SActionPlus = 2,
[Description("Full")]
Full = 3
}
public static IEnumerable<SelectListItem> GenerateSenStageList()
{
var values = from Stage e in Enum.GetValues(typeof(Stage))
select new { ID = (int)e, Name = e.ToDescription() };
var sellist= new SelectList(values, "Id", "Name", (int)Stage.BLANK);
return sellist;
}
public virtual ActionResult LogOn()
{
var res = GenerateSenStageList();
ViewData["StageList"] = res;
return View();
}
// the ToDescription() extension method
public static class Extn
{
public static string ToDescription(this Enum value)
{
FieldInfo fi = value.GetType().GetField(value.ToString());
var attributes =
(DescriptionAttribute[])fi.GetCustomAttributes(
typeof(DescriptionAttribute),
false);
if (attributes != null &&
attributes.Length > 0)
return attributes[0].Description;
else
return value.ToString();
}
}
// then in the LogOn view:
<%= Html.DropDownList("Stage", (IEnumerable<SelectListItem>)ViewData["StageList"])%>
this all works exactly as you'd hoped for, so I'm wondering if your invocation from the view is somehow getting a bit fuddled. try my example above and see if there are any subtle differences in the selectlist generated code etc.
Just a guess, but:
You cast the ViewData["StageList"] to IEnumerable<SelectListItem>. That enumerable may be the SelectList that you created in the Controller, but it does not have the SelectedValue property.
Maybe it works if you cast ViewData["StageList"] to SelectList instead?
<%= Html.DropDownList("Stage", (SelectList)ViewData["StageList"])%>
In this case using the interface may be the wrong thing, because you actually need the information provided by the SelectList object.
#Ken Wayne VanderLinde is right. If you are using C# 4.0, then you can do this to populate the selected value:
private static IEnumerable<SelectListItem> GenerateSenStageList()
{
var values = from Stage e in Enum.GetValues(typeof(Stage))
select new { ID = (int)e, Name = e.ToDescription() };
return new SelectList(values, "Id", "Name", values.Cast<dynamic>().Where(x => (x.ID == (int)Stage.BLANK)));
}

Categories