How do you switch View Engines on the fly within an ASP.Net MVC Controller action? - asp.net-mvc

I want to write a custom view engine that returns custom text (like coma delimited) does anyone know how I'd change the view engine on the fly to handle this?

I'd create a custom ActionResult. I use Json() function to return a JsonResult when I need JSON as response. I use this code to fill a ExtJS tree using JSON data.
public JsonResult Folders(string node)
{
var relativePath = (node == "root") ? "" : node;
var path = Path.Combine(BASE_PATH, relativePath);
var folder = new DirectoryInfo(path);
var subFolders = folder.GetDirectories();
var folders = new List<ExtJsTreeNode>();
foreach (var subFolder in subFolders)
{
folders.Add(new ExtJsTreeNode(subFolder.Name, subFolder.FullName.Replace(BASE_PATH, ""), "folder"));
}
return Json(folders);
}
private class ExtJsTreeNode
{
public string text { get; set; }
public string id { get; set; }
public string cls { get; set; }
public ExtJsTreeNode(string text, string id, string cls)
{
this.text = text;
this.id = id;
this.cls = cls;
}
}
A sample of a custom ActionResult here.

Your controller shouldn't know or care about this, other than which View to send the data to. The View can render in any format imaginable. I've got views that emit RSS (XML), etc. In the controller, either send it to the default view or explicitly identify the target view.

If I understood your question correctly, you want to use different views based on the parameters passed to the controller. If so, you can use this statement in the controller action:
return View("ViewName");
Otherwise, please clarify your question.

Related

How might I simultaneously bind FromQuery and FromRoute parameter?

I am needing to simultaneously support a query-parameter based route (/api/models?id=1) and a route based one (/api/models/1) while still allowing unambiguous access to the models collection (/api/models)?
My controller looks (something) like this:
[Route("/api/{controller}")]
public class ModelsController : Controller
{
[HttpGet]
public Models[] GetModels([FromQuery]QueryOptions queryOptions)
{
//...
}
[HttpGet("{id:int}")]
public Model Get([FromRoute] int id)
{
//...
}
[HttpGet("?{id:int}")]
public Model Get2Try1([FromQuery] int id)
{
//Fails with ": The literal section '?' is invalid.
//Literal sections cannot contain the '?' character."
//Which makes sense after some reading...
}
[HttpGet]
public Model Get2Try2([FromQuery] int id)
{
//Fails with "AmbiguousActionException: Multiple actions matched.
//The following actions matched route data and had all constraints satisfied:
//GetModels and Get2Try2"
//Which I think I understand as well...the absence of optional params
//means ambiguous routing...
}
[HttpGet] //What here?
public Model Get2Try3([FromQuery] int id) //and/or here?
{
}
}
I feel like there should be some way to (with declarative routing) accomplish this. Has anyone done anything along these lines?
Also, current code base is ASP.NET Core (RC1) to be upgraded to RTM/1.0 shortly. Details on either side are likely similar, but am interested in either/both.
I've found that the following works:
[HttpGet, Route("{id?}")]
... the key being mainly the '?'. You don't need any [FromX] in the method signature, this does the trick and caters for both query string and route parameter passing.
Unfortunately Swagger UI doesn't like it and expects some explicit parameter to work out of the box (https://github.com/domaindrivendev/Ahoy/issues/47 or https://github.com/domaindrivendev/Ahoy/issues/182), but that's another story :)
I had the same problem.
There aren't solutions that works (against wep api .net) with web api core.
If we set [Route("{id}")] and [Route("")] doesn't work; if we set only [Route("{id?}")] the query parameter is empty if I use querystring.
So, I've used a workround.
I used [Route("{id?}")], but inside the function I get param from Request.Query
Example
public T Cast<T>(string input)
{
T output = default(T);
if (string.IsNullOrWhiteSpace(input))
return output;
input = input.Trim();
try
{
Type typeToCastTo = typeof(T);
if (typeof(T).IsGenericType)
typeToCastTo = typeToCastTo.GenericTypeArguments[0];
if (typeToCastTo.IsEnum)
{
if (Enum.IsDefined(typeToCastTo, input))
return (T)Enum.Parse(typeToCastTo, input);
return output;
}
object value = Convert.ChangeType(input, typeToCastTo, CultureInfo.InvariantCulture);
return (value == null) ? output : (T)value;
}
catch
{
return output;
}
}
public void MapQuerystringParams<T>(ref T param, string name)
{
var q = Request.Query[name].FirstOrDefault();
if (q != null)
{
var cast = Cast<T>(q);
if (!cast.Equals(default(T)))
param = cast;
}
}
[Route("api/[controller]/[action]")]
[ApiController]
public class ActivityController : ControllerBase
{
//examples of call
//https://localhost:44345/api/Activity/GetActivityByCode/7000
//https://localhost:44345/api/Activity/GetActivityByCode/?Id=7000
[HttpGet]
[Route("{Id?}")]
public IActionResult GetActivityByCode(int Id)
{
MapQuerystringParams(ref Id, "Id"); //this take param from querystring if exists
ActivityBusiness business = new ActivityBusiness(new BusinessInitializer { config = configuration });
ActivityDTOModel activity = business.GetActivityByCode(Id);
return Ok(activity);
}
}
Ideally in the domain design if you can have one method serving one specific function then great. Recently I had to faithfully implement a legacy API and it wasn't an option for me to decompose the design of my API.
If you are suffering from ambiguous routes in MVC6 and need to differentiate unique Routes given specific QueryStrings that have been supplied at one single POST method. Then IActionConstraint can help! Here is some example code of me using it :
using System;
using Microsoft.AspNetCore.Mvc.ActionConstraints;
namespace Automation.Api.Service.Attributes
{
public class RoutingSpecificAttribute : Attribute, IActionConstraint
{
private string _keyParam;
public RoutingSpecificAttribute(string routingParameter)
{
this._keyParam = routingParameter;
}
public int Order
{
get
{
return 0;
}
}
public bool Accept(ActionConstraintContext context)
{
if (this._keyParam == null) { return true; }
switch (this._keyParam)
{
case "name": return context.RouteContext.HttpContext.Request.Query.ContainsKey(this._keyParam);
case "noquerystring": return context.RouteContext.HttpContext.Request.Query.Count == 0;
default:
return false;
}
}
}
}
This one method in the API that I needed to author both serviced a separate create + update functions based on the existence of a couple of QueryStrings: name & version.
So to help disambiguate you can distinctly decorate each of the methods within your controllers within said controller class [RoutingSpecific("noquerystring")] or [RoutingSpecific("name")] to help differentiate.
MSDN class description
Example implementation - see Entropy github
For anyone that happens to stumble upon this as I have,
Using .Net Core 3.1 the following works:
Web Controller Method
[HttpGet("something/{id}")]
public IActionResult Get([FromRoute] id, [FromQuery] OptionalParams optionalParams)
{
// do stuff
}
Query Parameter Container
public class OptionalParams
{
[FromQuery(Name = "colour_of_thing")]
public string Colour { get; set; }
[FromQuery(Name = "shape_of_thing")]
public string Shape { get; set; }
[FromQuery(Name = "some_other_filter")]
public string SomeOtherFilter { get; set; }
}
Url
var id = Guid.NewGuid();
var colour = "red";
var shape = "circle";
var url = $"Http://localhost:5000/something/{id}?colour_of_thing={colour}&shape_of_thing={shape}";

What templating library can be used with Asp .NET MVC?

In my MVC 5 app I need to be able to dynamically construct a list of fully qualified external URL hyperlinks, alone with some additional data, which will come from the Model passed in. I figure - I will need to construct my anchor tags something like this:
{{linkDisplayName}}
with AngularJS this would be natural, but, I have no idea how this is done in MVC.
Is there a templating library that can be used for this?
1) Create a model to Hold the Links
public class LinkObject
{
public string Link { get; set; }
public string Description { get; set; }
}
2) In your Action you can use ViewBag, ViewData or even pass the list inside you Model. I will show you how to do using ViewBag
public ActionResult MyDynamicView()
{
//Other stuff and code here
ViewBag.LinkList = new List<LinkObject>()
{
new LinkObject{ Link ="http://mylink1.com", Description = "Link 1"},
new LinkObject{ Link ="http://mylink2.com", Description = "Link 2"},
new LinkObject{ Link ="http://mylink3.com", Description = "Link 3"}
};
return View(/*pass the model if you have one*/);
}
3) In the View, just use a loop:
#foreach (var item in (List<LinkObject>)ViewBag.LinkList)
{
#item.Description
}
Just create a manual one for that, no need to do it from a template. For example, in javascript
function groupAnchor(url,display){
var a = document.createElement("a");
a.href = url;
a.className = "list-group-item";
a.target = "_blank";
a.innerHTML = display;
return a;
}
And then use that function to modify your html structure
<div id="anchors"></div>
<script>
document.getElementById("anchors").appendChild(groupAnchor("http://google.com","Google"));
</script>
Your approach to modification will more than likely be more advanced than this, but it demonstrates the concept. If you need these values to come from server side then you could always iterate over a set using #foreach() and issue either the whole html or script calls there -- or, pass the set from the server in as json and then use that in a function which is set up to manage a list of anchors.
To expand on this, it is important to avoid sending html to the view from a razor iteration. The reason being that html constructed by razor will increase the size of the page load, and if this is done in a list it can be a significant increase.
In your action, construct the list of links and then serialize them so they can be passed to the view
public ActionResult ViewWithLinks()
{
var vm = new ViewModel();
vm.Links = Json(LinkSource.ToList()).Data;
//or for a very simple test for proof of concept
var Numbers = Json(Enumerable.Range(0,100).ToList()).Data;
ViewData["numbers"] = Numbers ;
return View(vm);
}
where all you need is an object to hold the links in your view model
public class ViewModel
{
public ICollection<Link> Links { get; set; }
}
public class Link
{
public string text { get; set; }
public string href { get; set; }
}
and then in your view you can consume this json object
var allLinks = #Html.Raw(Json.Encode(Model.Links));
var numbersList = #Html.Raw(Json.Encode(ViewData["linkTest"]));//simple example
Now you can return to the above function in order to place it on the page by working with the array of link objects.
var $holder = $("<div>");
for(var i = 0; i < allLinks.length; i++){
$holder.append(groupAnchor(allLinks[i].href,allLinks[i].text));
}
$("#linkArea").append($holder);
The benefit is that all of this javascript can be cached for your page. It is loaded once and is capable of handling large amounts of links without having to worry about sending excessive html to the client.

Want to save selected (i.e., more than 1) enums as string with NHibernate

I cannot for the life of me get this to work with my existing code, but I am trying to save my enum selections as strings in NHibernate. Basically, I have a UI check box and if the user selects multiple check boxes I want to store those selections. Now, I can get NHibernate to store ONE selection (e.g., from a drop down or radio button list, where the user is limited to one choice only).
This is the jist of what I have for an enum:
public enum IncomeType
{
[Display(Name = "Full-Time Employment")]
FullTime,
[Display(Name = "Part-Time Employment")]
PartTime,
[Display(Name = "Self-Employment")]
SelfEmployed,
[Display(Name = "Rental")]
Rental,
[Display(Name = "Social Security Payments")]
SocialSecurity,
[Display(Name = "Retirement / Pension Payments")]
Retirement,
[Display(Name = "Child Support Payments")]
ChildSupport,
[Display(Name = "Spousal Maintenance")]
Maintenance,
[Display(Name = "Other")]
Other
}
I use a method to "select" whether a checkbox list is shown (if my BulkItemThreshold equals the number of options, a checkbox list is displayed). Here is that method:
public static IEnumerable<SelectListItem> GetItemsFromEnumString<T>
(T selectedValue = default(T)) where T : struct
{
return from name in Enum.GetNames(typeof(T))
let enumValue = Convert.ToString((T)Enum.Parse(typeof(T), name, true))
select new SelectListItem
{
Text = GetEnumDescription(name, typeof(T)),
Value = enumValue,
Selected = enumValue.Equals(selectedValue)
};
}
(Note: some items in there are helpers, but I don't believe they are relevant; also, the selected input is displayed using a template .cshtml file - again, not sure if that's relevant)
Now, I call this thusly:
public class IncomeTypeSelectorAttribute : SelectorAttribute
{
public override IEnumerable<SelectListItem> GetItems()
{
return Selector.GetItemsFromEnumString<IncomeType>();
}
}
And finally, we get to the virtual property (using a proxy) but this is where NHibernate throws a wrench (Note: this was working fine for me before NHibernate, and now I am trying to get many lines of code working with it WITHOUT having to re-do everything; if I re-do everything I will probably triple the code I already have to get it to work):
Property (record):
[IncomeTypeSelector(BulkSelectionThreshold = 9)]
public virtual List<string> IndividualIncomeTypeCheckBox { get; set; }
proxy (part):
public List<string> IndividualIncomeTypeCheckBox
{
get { return Record.IndividualIncomeTypeCheckBox; }
set { Record.IndividualIncomeTypeCheckBox = value; }
}
Again, this is how I was doing things and it was working great before NHibernate. But now I have to use NHibernate. No getting around it.
I am using a service class that it tying the two together in a Create method to save in the DB with NHibernate, and for the above it would ordinarily look like this:
part.IndividualIncomeTypeCheckBox = record.IndividualIncomeTypeCheckBox;
This would work if it were just one selection.
Well, I've spent a good two (2) months trying to get this to work. It's tough because I have lots of code where the user can make only one selection (such as with a radiobutton list) and it works GREAT - even with NHibernate. Let me give you an example:
public virtual IncomeType? IndividualIncomeTypeCheckBox { get; set; }
If I do the above, it will display a drop down list, and NHibernate will store the ONE allowable option selected by the user in the DB no problem. But more than one option with List<string> does not work.
Now, I have tried everything I could find here or elsewhere and nothing works. Yes, I know it should be IList<IncomeType> or some other variant. But if I use this then NHibernate requires that IncomeType be another table in the DB. This is too much code to write for such a simple thing I believe. We are not talking a many-to-many relation in the sense that this is not a User with Multiple addresses (wherein addresses would have street, city, state, zip, etc.).
I have tried different types of proxy get and set code, but nothing works. I have tried [Flags] and other things working with string only, but to no avail. Those last solutions would "work" but ONLY to save the first item selected out of multiple (i.e., in my scenario, if the user selected "FullTime" and "Rental" as Income Types, then only "FullTime" (string) would be saved or "1" ([Flags]/int), not both items selected.
I have a situation where I re-display the choices using a ReadOnly attribute like this:
[IncomeTypeSelector]
[ReadOnly(true)]
public List<string> IndividualIncomeTypeCheckBoxPost
{
get { return IndividualIncomeTypeCheckBox; }
}
This would display on the UI, but I tried doing something like this with NHibernate and it wouldn't work.
Could anyone please show me, using the above, how I can go about getting NHibernate to store more than one enum in this checkbox list scenario?
UPDATE:
More poking around here and on the web, and I came up with the following (which still does not work).
Property (record):
[IncomeTypeSelector(BulkSelectionThreshold = 9)]
public virtual IList<IncomeTypeRecord> IndividualIncomeTypeCheckBox
{
get { return incomeType; }
set { incomeType= value; }
}
private IList<IncomeTypeRecord> incomeType =
new List<IncomeTypeRecord>();
Proxy (part):
public IList<IncomeTypeRecord> IndividualIncomeTypeCheckBox
{
get { return Record.IndividualIncomeTypeCheckBox; }
set { Record.IndividualIncomeTypeCheckBox= value; }
}
And a change to the enum:
public enum IncomeType : int // removing int & value still gives validate error
{
[Display(Name = "Full-Time Employment")]
FullTime = 1,
[Display(Name = "Part-Time Employment")]
PartTime,
....
}
And I added this class to support IncomeTypeRecord
public class IncomeTypeRecord
{
public virtual int Id { get; set; }
public virtual IncomeType Value { get; set; }
}
HOWEVER, when I get to the UI screen and pick one or more options I get a validation error (value not valid). For example, say I pick FullTime alone, or pick FullTime and Retirement, then the UI will display the following error:
The value 'FullTime' is invalid.
The value 'FullTime,Retirement' is invalid.
(respectively)
Even if I remove the int declaration for the enum and get rid of the value I started with "1", I still get this validation error. I tried messing around with and adding different model binders (which now has me stumped as to whether my original problem still exists and now I have a different problem - but you still get bounty points :) ).
Pulling my hair out. If I could offer more bounty I would. I need a definitive solution. I appreciate any help.
UPDATE
This is what I have so far:
Record:
public virtual string IndividualIncomeTypeCheckBox{ get; set; }
Part:
//If I do IEnumberable<string> my .Select throws a cast error
public IEnumerable<IncomeType> IndividualIncomeTypeCheckBox
{
get
{
return Record
.IndividualIncomeTypeCheckBox
.Split(',')
.Select(r => (IncomeType)Enum.Parse(typeof(IncomeType), r));
}
set { Record.IndividualIncomeTypeCheckBox= value
== null ? null : String.Join(",", value); }
}
Service Class:
public SimplePart CreateSimple(SimplePartRecord record)
{
SimplePart simple = Services.ContentManager.Create<SimplePart>("Simple");
...
//How I would save a FirstName property (example Part / PartRecord below)
//public virtual string FirstName { get; set; } - PartRecord
//public string FirstName - Part
//{
// get { return Record.FirstName ; }
// set { Record.FirstName= value; }
//}
simple.FirstName = record.FristName;
...
//I obviously cannot do the following with the above IncomeType
//Getting cannot convert string to IEnumerable error
//How would I write this:
simple.IndividualIncomeTypeCheckBox = record.IndividualIncomeTypeCheckBox;
...
}
And this is how it's called in a controller (this persists to DB): (Updating Controller code)
public ActionResult Confirm(string backButton, string nextButton)
{
if (backButton != null)
return RedirectToAction("WrapUp");
else if ((nextButton != null) && ModelState.IsValid)
{
_myService.CreateSimple(myData.SimplePartRecord);
return RedirectToAction("Submitted");
}
else
return View(myData);
}
Updating with additional code (serialization and view model):
"myData" is defined in the controller (using Serialization) as:
private MyViewModel myData;
protected override void OnActionExecuting(ActionExecutingContext filterContext)
{
var serialized = Request.Form["myData"];
if (serialized != null)
{
myData = (MyViewModel)new MvcSerializer().Deserialize
(serialized, SerializationMode.Signed);
TryUpdateModel(myData);
}
else
myData = (MyViewModel)TempData["myData"] ?? new MyViewModel();
TempData.Keep();
}
protected override void OnResultExecuted(ResultExecutedContext filterContext)
{
if (filterContext.Result is RedirectToRouteResult)
TempData["myData"] = myData;
}
I use Serialization because I set up a multi-step wizard (as seen in the controller action "backButton" "nextButton) on the front-end. I am not using a driver (which can only display Admin or on the front-end but then only on .cshtml files directly under the ~/Views folder (not in a structured folder list like I am using)). No driver = no update view model type code = no mechanism to "create" the data in the DB. If I do not use some "create" type method, the form will submit but all the data will be "NULL".
When you say that the data should be persisted automatically, I am sorry but I do not see how. All the stuff I read or code I review has SOME method of updating the DB with whatever is entered in a form. If I am missing something, my apologies.
"MyViewModel" is pretty straightforward:
[Serializabel]
public class MyViewModel
{
public SimplePartRecord SimplePartRecord { get; set; }
}
And, just in case, here is the relevant portion of the migration (return 1 is a completely separate and unrelated table):
public int UpdateFrom1()
{
SchemaBuilder.CreateTable("SimplePartRecord",
table => table
.ContentPartRecord()
...
.Column("IndividualIncomeTypeCheckBox", DbType.String)
...
);
ContentDefinitionManager.AlterPartDefinition("SimplePart",
part => part
.Attachable(false));
return 2;
}
The error I am getting is
Cannot implicitly convert type 'string' to 'System.Collections.Generic.IEnumerable'"
when I do the following in the "Create" method of my service class:
simple.IndividualIncomeTypeCheckBox = record.IndividualIncomeTypeCheckBox;
One additional thought: I tried using the n-n Relation sample to handle this scenario. Aside from it being a lot of extra code for what I thought should be straightforward and simple, because of the way I am using Serialization I had a lot of object reference errors and could not figure out how to properly code my controller to handle it.
There's a lot of info to wade through here so hopefully I haven't missed the point. It appears to me that the goals are:
The business class has a collection property of IList<IncomeType> without requiring an additional table
The values in that collection should be persisted as a delimited string of the enum names
The best approach is to use a custom user type (an implementation of NHibernate.UserTypes.IUserType) to map the property. Below is a generic IUserType that will map an enum of type T from an IList<T> property to a comma delimited string in the database and back again. There's no easy way to restrict T to an enum but the code will only work with enums.
Mapping a property using the custom type is simple with Fluent NHibernate:
public class Person
{
public Person()
{
IncomeTypes = new List<IncomeType>();
}
public virtual int PersonId { get; protected set; }
public virtual string FirstName { get; set; }
public virtual string LastName { get; set; }
public virtual IList<IncomeType> IncomeTypes { get; protected set; }
}
public class PersonMap : ClassMap<Person>
{
public PersonMap()
{
Table("Person");
Id(x => x.PersonId).GeneratedBy.Identity();
Map(x => x.FirstName);
Map(x => x.LastName);
Map(x => x.IncomeTypes).CustomType<EnumAsDelimitedStringType<IncomeType>>();
}
}
And here's the code for the user type:
public class EnumAsDelimitedStringType<T> : IUserType
{
public new bool Equals(object x, object y)
{
if (ReferenceEquals(x, y))
{
return true;
}
var xList = x as IList<T>;
var yList = y as IList<T>;
if (xList == null || yList == null)
{
return false;
}
// compare set contents
return xList.OrderBy(xValue => xValue).SequenceEqual(yList.OrderBy(yValue => yValue));
}
public int GetHashCode(object x)
{
return x.GetHashCode();
}
public object NullSafeGet(IDataReader rs, string[] names, object owner)
{
var outValue = NHibernateUtil.AnsiString.NullSafeGet(rs, names[0]) as string;
if (string.IsNullOrEmpty(outValue))
{
return new List<T>();
}
var getValueArray = outValue.Split(new[] {','}, StringSplitOptions.RemoveEmptyEntries);
return Array.ConvertAll(getValueArray, s => (T)Enum.Parse(typeof(T), s)).ToList();
}
public void NullSafeSet(IDbCommand cmd, object value, int index)
{
var inValue = value as IList<T>;
// set to string.Empty if you prefer to store that instead of null when the collection is null or empty
object setValue = null;
if (inValue != null && inValue.Any())
{
var setValueArray = Array.ConvertAll(inValue.ToArray(), v => Enum.GetName(typeof(T), v));
setValue = string.Join(",", setValueArray);
}
NHibernateUtil.AnsiString.NullSafeSet(cmd, setValue, index);
}
public object DeepCopy(object value)
{
return value;
}
public object Replace(object original, object target, object owner)
{
return original;
}
public object Assemble(object cached, object owner)
{
return cached;
}
public object Disassemble(object value)
{
return value;
}
public SqlType[] SqlTypes
{
get { return new[] {new SqlType(DbType.AnsiString)}; }
}
public Type ReturnedType
{
get { return typeof(IList<T>); }
}
public bool IsMutable
{
get { return false; }
}
}
I think you're on the right track pursuing a [Flags] enum. You may have done this, but just in case -- making an enum flags-worthy is more than adding the attribute. You also have to specify the value for the items in a binary-friendly manner. I've found the easiest way to do this is as follows:
[Flags]
public enum IncomeType : long // you'll need the room with several options
{
FullTime = 1,
PartTime = 1 << 1,
SelfEmployed = 1 << 2
// And so on
}
If you don't do this, then you'll get sequential integer values, which breaks the bitwise comparison that allows you to do multiple values in a single integer.
Your code to create the SelectList looks fine. Your options should construct form values that get posted back with the same name. If you want to use the default modelbinder, that means the associated property on your view model would need to be List<int>. If you're not using a view model (you probably should) you can pull it out of the forms collection.
Once you have this set up, then translating from your view model to your NHibernate entity is simple if a little annoying. You basically have to cycle through the values in the list and |= them onto your NHibernate entity's single enum property.
So let's assume you have a view model like this:
public class MyEditViewModel
{
public string Name { get; set; }
public List<int> IncomeSelections { get; set; }
// You'll probably have this to populate the initial view rendering
public SelectList AllIncomeOptions { get; set; }
}
You'll build your view using your helpers and all that, then build the checkboxes using the SelectList but making sure the input name is IncomeSelections, then when it's posted back you will push the view model data into your NHibernate entity something like this:
var myNHEntity = new NHEntity();
// If you're editing an existing entity, then be sure to reset the enum
// value to 0 before going into the following foreach loop...
foreach (var incomeSelection in viewModel.IncomeSelections)
{
myNHEntity.IncomeSelection |= incomeSelection;
}
There's probably a more clever way to do this, and you might have to cast the int to your enum type, but you'll figure that out (I'd do it for you, but it is Friday and I already have a beer open).
NHibernate should persist it without you having to do anything funky on the NH side.
In summary...
It seems like this is more a problem of how you handle the posted data than the NHibernate side. If you implement something like this, then be sure to use Fiddler or FireBug to inspect the posted values to make sure 1) they're integers and 2) the names are the same so they'll be added to the list.
Good luck!
The problem is simply that it won't be able to map a List without building a full relationship with an intermediate association table. It is way simpler to have the record store the values as a comma-separated string (so your record property is a string, not a list of string) and your part can map back and forth between string and List.
You can find an example of something very close here:
https://bitbucket.org/bleroy/nwazet.commerce/src/d722cbebea525203b22c445905c9f28d2af7db46/Models/ProductAttributesPartRecord.cs?at=default
https://bitbucket.org/bleroy/nwazet.commerce/src/d722cbebea525203b22c445905c9f28d2af7db46/Models/ProductAttributesPart.cs?at=default
It's not using enum values, instead it's a list of ids, but that should give you a good idea about how to make this work fairly simply: parsing enums you already know how to do.
Let me know if you need more details, but I think that's what you needed to get unblocked.

In MVC, why does the routeValues property in RedirectToAction() not accept my class as argument?

So here's the deal, i want to be able to export any Enumerable of items to excel:
Here's an ActionMethod in some Area of my app that constructs an "ExportToExcel" model, then Redirects it to an Action Method in another controller and another are which does all the formatting-to-excel work:
public ActionResult ExportCustomListToExcel()
{
var exportModel = new ExportToExcelModel();
//Here I fill up the model with a dataTable / other file info like
//exportModel.Items = blah blah..
return RedirectToAction("ExportToExcel", "Shared", new { model = exportModel, testString = "test", area = "Shared" });
}
And here's my Shared ExportToExcel ActionMethod:
public ActionResult ExportToExcel(ExportToExcelModel model, string testString)
{
//PROBLEM IS RIGHT HERE!
// where testString == "test"
// but model == null :(
//Ommited unrelated code
}
My ExportToExcel actionMethod gets hit, but somewhere along the way my ExportToExcelModel gets lost :(
Note: It succeeds on passing strings like "testString" so is there somwthing wrong with my model?
Just in case, the ExportToExcelModel is:
public class ExportToExcelModel
{
public ExportToExcelModel() {}
public ExportToExcelModel(string fileName, ItemType itemType, IEnumerable<ExportableToExcelItem> items)
{
this.FileName = fileName;
this.ItemType = ItemType;
this.Items = items;
}
public string FileName { get; set; }
public ItemType ItemType { get; set; }
public IEnumerable<ExportableToExcelItem> Items { get; set; }
}
Thanks in advance!
First time i've ever needed to actually ask a question here since every other question i've ever had i've found already answered here :)
EDIT: Posting FormCollection results:
http://imageshack.us/photo/my-images/861/sinttulonsa.png
Sorry, newbies cant post pics :(
The reason is that a RedirectToAction result will launch a GET request and your parameters will have to be passed along through the querystring. Obviously there is a limit to the amount of characters a url can consist of.
Seems to me that you should do the conversion to Excel in a class instead of behind another Action.
So CustomExportAction1 and CustomExportAction2 both call
return File(ExcelExporter.ExportExcel(dataToExport));
or something similar.
try to switch your ExportToExcel signature to
public ActionResult ExportToExcel(FormCollection data)
{
var model = new ExportToExcelModel();
try
{
UpdateModel(model, data)
}
catch(UpdateModelException ex)
{
}
}
look at what's in the FormCollection (that might help), and also see if UpdateModel is throwing an exception, because this is what is happening behind the seen when you make your action method take in a model instead of a FormCollection.
Hope that help you track it down
UPDATE:
You might have to do it using TempData, read this, supposedly you can't do this out of the box with ASP.NET MVC!!

MVC3 – ViewModels and controller functionalty: suggested design patterns

I have built a simple MVC3-based ticket entry site for a less-than-usable call center application and am attempting to refactor my prototype to better adhere to design patterns partly to make it more maintainable going forward but mostly as a learning exercise.
The user-facing view is a form consisting of basic user information in addition to a few panels allowing selection of various resource types. Each resource type (hardware, software, etc) is displayed in the same way: using dual, filterable listboxes with add/remove buttons, an optional “justification” textarea that conditionally displays if a requested resource requires justification, and general comments.
I have built the following ViewModel for the individual panels:
public class RequestableList
{
// list of requestable items ids requiring justification
private List<string> _restrictedItems = new List<string>();
public List<string> RestrictedItems
{
get { return _restrictedItems; }
set { _restrictedItems = value; }
}
// the key-value pairs from which to populate available items list
private Dictionary<string, string> _availableItems = new Dictionary<string, string>();
public Dictionary<string, string> AvailableItems
{
get { return _availableItems; }
set { _availableItems = value; }
}
// item ids requested by user
private List<string> _requestedItems = new List<string>();
public List<string> RequestedItems
{
get { return _requestedItems; }
set { _requestedItems = value; }
}
}
The main ViewModel is then comprised of multiple RequestableLists as necessary:
public class SimpleRequestViewModel
{
public UserInfo userInfo { get; set; }
public RequestableList Software {get;set;}
public RequestableList Hardware {get;set;}
public RequestableList Access {get;set;}
public string SoftwareAdditionalInfo { get; set; }
public string HardwareAdditionalInfo { get; set; }
public string AccessFileMailShare { get; set; }
public string AccessAdditionalInfo { get; set; }
public string SoftwareJustification { get; set; }
public string HardwareJustification { get; set; }
public string AccessJustification { get; set; }
public string Comment { get; set; }
}
I have created a strongly typed view for SimpleRequestViewModel (and its variant) and a strongly typed EditorTemplate for RequestableList that wires up the dual listboxes, filtering, and jquery. All renders well and is working but the code currently smells.
When posting to the controller, if the model is valid I must translate it into a readable text description in order to create a new ticket in in the call center app. It doesn’t feel right to have the controller performing that translation into readable text but I run into hurdles when trying to design another class to translate the viewmodels.
Only the selected item values are posted so before translating the request into text I must first lookup the appropriate text for the provided values (they are required in description). The controller is currently the only object that has access to the call center data model for this lookup query.
There are 2 similar ViewModels containing varying combinations of RequestableLists so any translator must be able to translate the various combinations. One has only Hardware and Software, another may have Hardware Software, and a few more RequestableLists.
I considered overriding ToString() directly in the ViewModel but didn’t like that business logic (conditional rendering) there, and again, once posted, the ViewModel doesn’t contain the text for the selected items in the listbox so it would need access to the data model.
The translation of posted values to text as it is currently handled in the controller smells as it’s handled in a switch statement. The controller takes each posted RequestableList and populates the original “Available” fields before it builds the new ticket description.
switch (requestCategory)
{
case RequestableCategory.Software:
itemList = sde.GetSoftware();
break;
case RequestableCategory.Hardware:
itemList = sde.GetHardware();
break;
case RequestableCategory.Access:
itemList = sde.GetAccess();
break;
case RequestableCategory.Telecom:
itemList = sde.GetTelecom();
break;
default:
throw new ArgumentException();
}
So, my question(s):
What patterns are techniques would you recommend for performing the posted viewmodel to ticket description translation?
How do you typically handle the “only posts value” issue with select boxes when you need the text as well as the value?
Is there a better way for me to be approaching this problem?
Again, I am hoping this is a learning experience for me and am more than willing to provide additional information or description if needed.
A few suggestions:
Abstract the logic that does the call center submission into its own class. Provide (from the controller) whatever dependencies it needs to access the call center DB. Have different methods to handle the various types of view models using overloading. Presumably the descriptions come from the DB so you can extract the description from the DB based on the value in this class. This class could also take responsibility for building your view models for the display actions as well. Note that with this pattern the class can interact with the DB directly, through a repository, or even via web services/an API.
Use a repository pattern that implements some caching if performance is an issue in looking up the description from the DB the second time. I suspect it won't be unless your call center is very large, but that would be the place to optimize the query logic. The repository can be the thing that the controller passes to the submission class.
If you don't need to access the DB directly in the controller, consider passing the broker class as a dependency directly.
It might look like:
private ICallCenterBroker CallCenterBroker { get; set; }
public RequestController( ICallCenterBroker broker )
{
this.CallCenterBroker = broker;
// if not using DI, instantiate a new one
// this.CallCenterBroker = broker ?? new CallCenterBroker( new CallCenterRepository() );
}
[HttpGet]
public ActionResult CreateSimple()
{
var model = this.CallCenterBroker.CreateSimpleModel( this.User.Identity.Name );
return View( model );
}
[HttpPost]
public ActionResult CreateSimple( SimpleRequestViewModel request )
{
if (Model.IsValid)
{
var ticket = this.CallCenterBroker.CreateTicket( request );
// do something with ticket, perhaps create a different model for display?
this.CallCenterBroker.SubmitTicket( ticket );
return RedirectToAction( "index" ); // list all requests?
}
return View();
}

Resources