Trying to do something simple with mvc in umbraco but kind of lost after doing some stuff. So here is what I am trying to do:
I have a simple one page site it has a master and home view. Home inherits from master. I am trying to create a search form on the home page. So I used the scaffold nuget package and did
I updated my model class and updated my properties to Dictionary<string,string> and added constructor, here i fill the dictionaries with id and node name from diffrent parts of my site. My properties look like
public Dictionary<string,string> Weight {get; set;}
public Dictionary<string, string> Thickness { get; set; }
[Display(Name = "Min Temperature Resistance")]
public Dictionary<string, string> MinTemp { get; set; }
[Display(Name = "Max Temperature Resistance")]
public Dictionary<string, string> MaxTemp { get; set; }
[Display(Name = "Primary Chemical Resistance")]
public Dictionary<string, string> ChemicalResistance { get; set; }
[Display(Name = "Tensile Strength")]
public Dictionary<string, string> TensileStrength { get; set; }
I then created a partial view called it SearchForm and added code to render out dropdown lists for the above properties on my model. In my home page view i do @Html.Partial("SearchForm",new TEF.Web.Models.SearchFormModel()) that writes out the form and dropdowns. I suspect that I should not be doing this but instead calling my view in SearchFormSurface folder? So how do i call that in my home view.
In the old way of doing this i would just create a macro to write out the form and drop it on template. I was trying to do it the mvc way and no doubt am doing it wrong?
Hiya Ismail, After chatting with you on Skype ypour issue is how your calling/requesting it: Use something like this @Html.Action("SearchForm", "SearchFormSurface") or similar
Awesome so i now have the form. I need to validate my model however its 6 fields each one is dictioanry<string,string> if on the form at least one dropdown is selected then its valid.
My model has 6 properties of type Dictionary<string,string> they then in the view are used to create dropdowns using code in razor like
@Html.DropDownListFor(model => model.Weight.Keys,
new SelectList(
Model.Weight,
"Key",
"Value"),"Please select")
Found the code above on stack overflow. Dropdowns render fine on post back state is maintained. So i wanted to add validation figured i would need custom validation. Basically at least one of the dropdowns needs to be selected dont care which. So i realised i need custom validator however when you create one its within context of one property so you would have
[MycustomValidator]
public Dictionary<string,string> Weight {get; set;}
How do in the validator get the other values on the model. So Warren suggested just do the validation in the SurfaceController there is method to handle the form
//endpoint for posting data to a surface controller action
[HttpPost]
public ActionResult HandleSearchForm(SearchFormModel model)
In the controller the model never has values. I suspect I need a custom model binder? Although digging around mvc4 should handle model binding to a dictionary<string,string>
TempData.Add("SearchFormError", "Please select at least on option to perform search");
return RedirectToCurrentUmbracoPage();
}
TempData is always empty. However if after adding the search results to the TempData dictionary i do RedirectToCurrentUmbracoPage instead of CurrentUmbracoPage then i have results in the TempData. However then on my form i lose selected values in my dropdowns on my view. So am i doing something wrong here do i need to update my view so that previously selected values are restored?
So Matt Brailsford just pointed out its a bug?? see http://issues.umbraco.org/issue/U4-1339 so anyone got suggestions? Do i need to write code in my view to set selected values in my dropdowns?
I would really try and remove your dependencys on RedirectToCurrentUmbracoPage and stick to tradtional MVC return view or partial. If your call is from a partial you will need to return to a partial vew.
How come you have your bussiness logic in the controller and not in a class? This would offer some level of abstraction and sep of concerns? There could be some good reason for this and i am sorry if there is, only read a bit.
1. Yes i would. If you have problems then you know its not being caused by these calls.
2. Yea all that logic the controller does not need to know about. Things like ContextItems.Add and ContextItems.Remove. Your controller should not be doing this imo. Its the result of this the controller should worry about and not actual doing.
3.Yea does not seem any compelling reason why you would need to do that, they are all ways going to be null or empty by the looks of it :). Charlie :).
Ok so done some refactoring. I have got ninject in the mix and am now injecting in my search service all works nicely blog post to follow it provided to be very simple thankfully the nuget install package for ninject does all the monkey work for you all you have to do is wire up.
So here is next question. My model in certain I am doing it wrong. I am hydrating the model in the constructor so i have
I already have public properties on my model of type Dictionary<string,string> then in my view they write out dropdown lists the complete model looks like
public class SearchFormModel : RenderModel
{
#region model properties public
public Dictionary<string, string> Weight { get; set; }
public Dictionary<string, string> Thickness { get; set; }
[UmbracoDisplayLocalised("MinTempResistance")]
public Dictionary<string, string> MinTemp { get; set; }
[UmbracoDisplayLocalised("MaxTempResistance")]
public Dictionary<string, string> MaxTemp { get; set; }
[UmbracoDisplayLocalised("PrimaryChemRes")]
public Dictionary<string, string> ChemicalResistance { get; set; }
[UmbracoDisplayLocalised("TensileStrength")]
public Dictionary<string, string> TensileStrength { get; set; }
using System; using System.Collections.Generic; using System.Linq; using System.Text; using umbraco; using umbraco.interfaces; using umbraco.NodeFactory; using Umbraco.Web; using Umbraco.Web.Models;
namespace ClassLibrary2 { public class Class1 { public class SearchFormModel : RenderModel {
#region model properties public
public IList <INode> NodeChildrenList { get; set; }
public Dictionary<string, string> Weight { get; set; }
public Dictionary<string, string> Thickness {get;set;}
[UmbracoDisplayLocalised("MinTempResistance")]
public Dictionary<string, string> MinTemp { get; set; }
[UmbracoDisplayLocalised("MaxTempResistance")]
public Dictionary <string, string> MaxTemp { get; set; }
[UmbracoDisplayLocalised("PrimaryChemRes")]
public Dictionary<string, string> ChemicalResistance { get; set; }
[UmbracoDisplayLocalised("TensileStrength")]
public Dictionary<string, string> TensileStrength { get; set; }
if (selectValue.Contains("+")) { int fieldValue = isMm ? (int) decimal.Parse(selectValue)*10 : int.Parse(selectValue); selectValues.Add(fieldValue.ToString("D6") + "-" + Constants.Examine.MaxValue, node.Name); }
else { //we have min and max so put both in string[] values = selectValue.Split('-'); int valueMin = isMm ? (int)(float.Parse(values[0]) * 10) : int.Parse(values[0]); int valueMax = isMm ? (int)(float.Parse(values[1]) * 10) : int.Parse(values[1]); selectValues.Add(valueMin.ToString("D6") + "-" + valueMax.ToString("D6"),node.Name); }
As part of your code changes you have public IList <INode> NodeChildrenList { get; set; } but its not used anywhere also I suspect that will be injected in somehow?
Umbraco with mvc
Guys,
Trying to do something simple with mvc in umbraco but kind of lost after doing some stuff. So here is what I am trying to do:
I have a simple one page site it has a master and home view. Home inherits from master. I am trying to create a search form on the home page. So I used the scaffold nuget package and did
Scaffold SurfaceController SearchForm Weight,Thickness,MinTemp,MaxTemp,ChemicalResistance,TensileStrength
that gave me
Hiya Ismail,
After chatting with you on Skype ypour issue is how your calling/requesting it:
Use something like this @Html.Action("SearchForm", "SearchFormSurface") or similar
http://24days.in/umbraco/2012/creating-a-login-form-with-umbraco-mvc-surfacecontroller/
Hopefully this has helped you out.
Cheers,
Warren
Awesome so i now have the form. I need to validate my model however its 6 fields each one is dictioanry<string,string> if on the form at least one dropdown is selected then its valid.
Regards
Ismail
Ismail, what is the problem validating your model? :). Charlie.
Charles,
My model has 6 properties of type Dictionary<string,string> they then in the view are used to create dropdowns using code in razor like
@Html.DropDownListFor(model => model.Weight.Keys,
new SelectList(
Model.Weight,
"Key",
"Value"),"Please select")
Found the code above on stack overflow. Dropdowns render fine on post back state is maintained. So i wanted to add validation figured i would need custom validation. Basically at least one of the dropdowns needs to be selected dont care which. So i realised i need custom validator however when you create one its within context of one property so you would have
[MycustomValidator]
public Dictionary<string,string> Weight {get; set;}
How do in the validator get the other values on the model. So Warren suggested just do the validation in the SurfaceController there is method to handle the form
//endpoint for posting data to a surface controller action
[HttpPost]
public ActionResult HandleSearchForm(SearchFormModel model)
In the controller the model never has values. I suspect I need a custom model binder? Although digging around mvc4 should handle model binding to a dictionary<string,string>
Regards
Ismail
Guys,
Made some progress with this. Warren suggested i do the validation in the surface control so i know have
//endpoint for posting data to a surface controller action
[HttpPost]
public ActionResult HandleSearchForm(SearchFormModel model)
{
//model not valid, do not save, but return current umbraco page
if (Valid(model))
{
//do a search add results to tempdata
return CurrentUmbracoPage();
}
TempData.Add("SearchFormError", "Please select at least on option to perform search");
return RedirectToCurrentUmbracoPage();
}
private bool Valid(SearchFormModel model)
{
if (string.IsNullOrEmpty(model.ChemicalResistance["Keys"])
&& string.IsNullOrEmpty(model.MaxTemp["Keys"])
&& string.IsNullOrEmpty(model.MinTemp["Keys"])
&& string.IsNullOrEmpty(model.TensileStrength["Keys"])
&& string.IsNullOrEmpty(model.Weight["Keys"])
&& string.IsNullOrEmpty(model.Thickness["Keys"]))
{
return false;
}
return true;
}
which works.
Regards
Ismail
Guys,
Ok so now different issue. I have the following in my controller:
[HttpPost]
public ActionResult HandleSearchForm(SearchFormModel model)
{
//model not valid, do not save, but return current umbraco page
if (Valid(model))
{
//do a search add results to tempdata
var query = new Dictionary<string, string>();
query = CreateQuery(model);
TempData.Add("SearchResults", SearchService.Instance.Search(query).ToList());
return CurrentUmbracoPage();
}
TempData.Add("SearchFormError", "Please select at least on option to perform search");
return RedirectToCurrentUmbracoPage();
}
TempData is always empty. However if after adding the search results to the TempData dictionary i do RedirectToCurrentUmbracoPage instead of CurrentUmbracoPage then i have results in the TempData. However then on my form i lose selected values in my dropdowns on my view. So am i doing something wrong here do i need to update my view so that previously selected values are restored?
Regards
Ismail
Ok,
So Matt Brailsford just pointed out its a bug?? see http://issues.umbraco.org/issue/U4-1339 so anyone got suggestions? Do i need to write code in my view to set selected values in my dropdowns?
Regards
Ismail
Right with bit of help from Matt I know have
public ActionResult HandleSearchForm(SearchFormModel model)
{
//model not valid, do not save, but return current umbraco page
if (Valid(model))
{
//do a search add results to tempdata
string examineQuery = string.Empty;
var query = new Dictionary<string, string>();
query = CreateQuery(model);
HttpContext.Items.Add("SearchResults", SearchService.Instance.Search(query, out examineQuery).ToList());
HttpContext.Items.Add("generatedQuery", examineQuery);
return CurrentUmbracoPage();
}
HttpContext.Items.Remove("SearchResults");
HttpContext.Items.Remove("generatedQuery");
TempData.Add("SearchFormError", "Please select at least on option to perform search");
return RedirectToCurrentUmbracoPage();
}
works probably not the best way todo things but hey ho!!
I would really try and remove your dependencys on RedirectToCurrentUmbracoPage and stick to tradtional MVC return view or partial. If your call is from a partial you will need to return to a partial vew.
How come you have your bussiness logic in the controller and not in a class? This would offer some level of abstraction and sep of concerns? There could be some good reason for this and i am sorry if there is, only read a bit.
Why are you doing:
HttpContext.Items.Remove("SearchResults");
HttpContext.Items.Remove("generatedQuery");
if the model state is invalid?
Charles,
Time to refactor. So
1. Will try and get rid of RedirectToCurrentUmbracoPage
2. By business logic you mean the search code which idealy should be injected in? The search is done in another assembly and not in the controller
3. Will clearup that code when model is invalid as you say its not needed.
Regards
Ismial
1. Yes i would. If you have problems then you know its not being caused by these calls.
2. Yea all that logic the controller does not need to know about. Things like ContextItems.Add and ContextItems.Remove. Your controller should not be doing this imo. Its the result of this the controller should worry about and not actual doing.
3.Yea does not seem any compelling reason why you would need to do that, they are all ways going to be null or empty by the looks of it :). Charlie :).
Charles,
Ok so done some refactoring. I have got ninject in the mix and am now injecting in my search service all works nicely blog post to follow it provided to be very simple thankfully the nuget install package for ninject does all the monkey work for you all you have to do is wire up.
So here is next question. My model in certain I am doing it wrong. I am hydrating the model in the constructor so i have
public SearchFormModel()
: this(new UmbracoHelper(UmbracoContext.Current).TypedContent(UmbracoContext.Current.PageId))
{
Init();
}
public SearchFormModel(IPublishedContent content)
: base(content)
{
Init();
}
private void Init()
{
var nodes = uQuery.GetNode(1066).ChildrenAsList;
ChemicalResistance = FillList(nodes);
nodes = uQuery.GetNode(1074).ChildrenAsList; //tensile strength
TensileStrength = FillRange(nodes, "N/5mm");
nodes = uQuery.GetNode(1054).ChildrenAsList;
Weight = FillRange(nodes, "gsm");
nodes = uQuery.GetNode(1057).ChildrenAsList;
Thickness = FillRange(nodes, "mm", true);
nodes = uQuery.GetNode(1060).ChildrenAsList;
MinTemp = FillTemperatureRange(nodes);
nodes = uQuery.GetNode(1063).ChildrenAsList;
MaxTemp = FillTemperatureRange(nodes);
}
In my view this binds each property to dropdown list. So how should i be hydrating my model? It all works but I want to do it right.
Regards
Ismail
Hi, what do you mean hydrating your model? All you are doing at the moment is calling a method from your constructor? What are you trying to do?.
By the looks of things you properties and methods?
Could you tell me what FillTemperatureRange, FillTemperatureRange and FillList are and i will help. :). Charlie.
You will need some public properties though like:
public someType Weight {get;set;}
Charlie,
I already have public properties on my model of type Dictionary<string,string> then in my view they write out dropdown lists the complete model looks like
public class SearchFormModel : RenderModel
{
#region model properties public
public Dictionary<string, string> Weight { get; set; }
public Dictionary<string, string> Thickness { get; set; }
[UmbracoDisplayLocalised("MinTempResistance")]
public Dictionary<string, string> MinTemp { get; set; }
[UmbracoDisplayLocalised("MaxTempResistance")]
public Dictionary<string, string> MaxTemp { get; set; }
[UmbracoDisplayLocalised("PrimaryChemRes")]
public Dictionary<string, string> ChemicalResistance { get; set; }
[UmbracoDisplayLocalised("TensileStrength")]
public Dictionary<string, string> TensileStrength { get; set; }
#endregion
#region constructors
public SearchFormModel()
: this(new UmbracoHelper(UmbracoContext.Current).TypedContent(UmbracoContext.Current.PageId))
{
Init();
}
public SearchFormModel(IPublishedContent content)
: base(content)
{
Init();
}
#endregion
#region private methods
private void Init()
{
var nodes = uQuery.GetNode(1066).ChildrenAsList;
ChemicalResistance = FillList(nodes);
nodes = uQuery.GetNode(1074).ChildrenAsList; //tensile strength
TensileStrength = FillRange(nodes, "N/5mm");
nodes = uQuery.GetNode(1054).ChildrenAsList;
Weight = FillRange(nodes, "gsm");
nodes = uQuery.GetNode(1057).ChildrenAsList;
Thickness = FillRange(nodes, "mm", true);
nodes = uQuery.GetNode(1060).ChildrenAsList;
MinTemp = FillTemperatureRange(nodes);
nodes = uQuery.GetNode(1063).ChildrenAsList;
MaxTemp = FillTemperatureRange(nodes);
}
private Dictionary<string, string> FillRange(IEnumerable<INode> nodes, string replaceUnit, bool isMm = false)
{
var selectValues = new Dictionary<string, string>();
foreach (var node in nodes)
{
string displayValue = node.Name;
string selectValue = string.Empty;
if (CanInjectDummyMin(node.Name))
{
selectValue = Constants.Examine.MinValue + "-" + Constants.Examine.MinValue;
}
else
{
selectValue = node.Name.Replace(replaceUnit, "").TrimEnd();
if (selectValue.Contains("+"))
{
int fieldValue = 0;
if (isMm)
{
decimal tmp = decimal.Parse(selectValue.Replace("+", ""))*10;
fieldValue = (int) tmp;
}
else
{
//only one value inject that and a fake max
fieldValue = int.Parse(selectValue.Replace("+", ""));
}
selectValue = fieldValue.ToString("D6") + "-" + Constants.Examine.MaxValue;
}
else
{
//we have min and max so put both in
string[] values = selectValue.Split('-');
int valueMin = 0;
int valueMax = 0;
if (isMm)
{
valueMin = (int) (float.Parse(values[0])*10);
valueMax = (int) (float.Parse(values[1])*10);
}
else
{
valueMin = int.Parse(values[0]);
valueMax = int.Parse(values[1]);
}
selectValue = valueMin.ToString("D6") + "-" + valueMax.ToString("D6");
}
}
selectValues.Add(selectValue, displayValue);
}
return selectValues;
}
private Dictionary<string, string> FillTemperatureRange(IEnumerable<INode> nodes)
{
var selectValues = new Dictionary<string, string>();
foreach (var node in nodes)
{
string fieldRaw = node.Name.Replace("°C", "").Replace("minus", "");
string displayValue = node.Name;
string selectValue = string.Empty;
if (CanInjectDummyMin(node.Name))
{
selectValue =Constants.Examine.MinValue + "-" + Constants.Examine.MinValue;
}
else
{
if (fieldRaw.Contains("+"))
{
int fieldValue = 0;
//only one value inject that and a fake max
fieldValue = int.Parse(fieldRaw.Replace("+", ""));
selectValue = fieldValue.ToString("D6") + "-" + Constants.Examine.MaxValue;
}
else
{
//we have min and max so put both in
string[] values = fieldRaw.Split('-');
int valueMin = 0;
int valueMax = 0;
valueMin = int.Parse(values[0]);
valueMax = int.Parse(values[1]);
selectValue = valueMin.ToString("D6") + "-" + valueMax.ToString("D6");
}
}
selectValues.Add(selectValue, displayValue);
}
return selectValues;
}
private Dictionary<string, string> FillList(IEnumerable<INode> nodes)
{
var values = new Dictionary<string, string>();
foreach (var node in nodes)
{
if (CanInjectDummyMin(node.Name))
{
values.Add(Constants.Examine.MinValue + "-" + Constants.Examine.MinValue, node.Name);
}
else
{
values.Add(node.Id.ToString(), node.Name);
}
}
return values;
}
private bool CanInjectDummyMin(string value)
{
string raw = value;
if (raw.ToLower().Contains("know"))
{
return true;
}
return false;
}
#endregion
}
Ok, will have a look :)
Hi, what do these do? UmbracoDisplayLocalised i see they are Umbraco MVC localised data annotations? Look intresting :D
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using umbraco;
using umbraco.interfaces;
using umbraco.NodeFactory;
using Umbraco.Web;
using Umbraco.Web.Models;
namespace ClassLibrary2
{
public class Class1
{
public class SearchFormModel : RenderModel
{
#region model properties public
public IList <INode> NodeChildrenList { get; set; }
public Dictionary<string, string> Weight { get; set; }
public Dictionary<string, string> Thickness {get;set;}
[UmbracoDisplayLocalised("MinTempResistance")]
public Dictionary<string, string> MinTemp { get; set; }
[UmbracoDisplayLocalised("MaxTempResistance")]
public Dictionary <string, string> MaxTemp { get; set; }
[UmbracoDisplayLocalised("PrimaryChemRes")]
public Dictionary<string, string> ChemicalResistance { get; set; }
[UmbracoDisplayLocalised("TensileStrength")]
public Dictionary<string, string> TensileStrength { get; set; }
#endregion
#region constructors
public SearchFormModel()
: this(new UmbracoHelper(UmbracoContext.Current).TypedContent(UmbracoContext.Current.PageId))
{
Init();
}
public SearchFormModel(IPublishedContent content)
: base(content)
{
Init();
}
#endregion
#region private methods
private IList<INode> GetNodeChildren(int nodeId)
{
return uQuery.GetNode(nodeId).ChildrenAsList;
}
private enum ReplacementUnit
{
N5mm,
gsm,
mm
}
private void Init()
{
ChemicalResistance = FillList(GetNodeChildren(1066));
TensileStrength = FillRange(GetNodeChildren(1074), ReplacementUnit.N5mm);
Weight = FillRange(GetNodeChildren(1054), ReplacementUnit.gsm);
Thickness = FillRange(GetNodeChildren(1057), ReplacementUnit.mm, true);
MinTemp = FillTemperatureRange(GetNodeChildren(1060));
MaxTemp = FillTemperatureRange(GetNodeChildren(1063));
}
private Dictionary<string, string> FillRange(IEnumerable<INode> nodes, ReplacementUnit replaceUnit, bool isMm = false)
{
Dictionary <string,string> selectValues = new Dictionary<string, string>();
foreach (INode node in nodes)
{
if (CanInjectDummyMin(node.Name))
{
return Constants.Examine.MinValue + "-" + Constants.Examine.MinValue;
}
string selectValue = node.Name.Replace(replaceUnit.ToString(), "").TrimEnd().Replace("+","");
if (selectValue.Contains("+"))
{
int fieldValue = isMm
? (int) decimal.Parse(selectValue)*10
: int.Parse(selectValue);
selectValues.Add(fieldValue.ToString("D6") + "-" + Constants.Examine.MaxValue, node.Name);
}
else
{
//we have min and max so put both in
string[] values = selectValue.Split('-');
int valueMin = isMm ? (int)(float.Parse(values[0]) * 10) : int.Parse(values[0]);
int valueMax = isMm ? (int)(float.Parse(values[1]) * 10) : int.Parse(values[1]);
selectValues.Add(valueMin.ToString("D6") + "-" + valueMax.ToString("D6"),node.Name);
}
}
return selectValues;
}
private Dictionary<string, string> FillTemperatureRange(IEnumerable<INode> nodes)
{
Dictionary <string,string> selectValues = new Dictionary<string, string>();
foreach (Node node in nodes)
{
string fieldRaw = node.Name.Replace("°C", "").Replace("minus", "");
string displayValue = node.Name;
string selectValue;
if (CanInjectDummyMin(node.Name))
{
selectValue = Constants.Examine.MinValue + "-" + Constants.Examine.MinValue;
}
else
{
if (fieldRaw.Contains("+"))
{
int fieldValue = 0;
//only one value inject that and a fake max
fieldValue = int.Parse(fieldRaw.Replace("+", ""));
selectValue = fieldValue.ToString("D6") + "-" + Constants.Examine.MaxValue;
}
else
{
//we have min and max so put both in
string[] values = fieldRaw.Split('-');
int valueMin = 0;
int valueMax = 0;
valueMin = int.Parse(values[0]);
valueMax = int.Parse(values[1]);
selectValue = valueMin.ToString("D6") + "-" + valueMax.ToString("D6");
}
}
selectValues.Add(selectValue, displayValue);
}
return selectValues;
}
private Dictionary<string, string> FillList(IEnumerable<INode> nodes)
{
var values = new Dictionary<string, string>();
foreach (var node in nodes)
{
if (CanInjectDummyMin(node.Name))
{
values.Add(Constants.Examine.MinValue + "-" + Constants.Examine.MinValue, node.Name);
}
else
{
values.Add(node.Id.ToString(), node.Name);
}
}
return values;
}
private bool CanInjectDummyMin(string value)
{
string raw = value;
if (raw.ToLower().Contains("know"))
{
return true;
}
return false;
}
#endregion
}
}
}
Charles,
About the annotations see http://ismailmayat.wordpress.com/2013/07/25/umbraco-6-mvc-and-dependency-injection/ thanks for the updated code will take a look.
Regards
Ismail
Charles,
As part of your code changes you have public IList <INode> NodeChildrenList { get; set; } but its not used anywhere also I suspect that will be injected in somehow?
Regards
Ismail
Sorry for late responce will have a look at replys
When you say injected in are you refering to IOC / constructor injection ect?
I had litrally put in in there to remove some of the duplication you had in your code :)
is working on a reply...