I'm trying to get some json file from url using service and show them in my application.
Here's how my code looks like now...
Model:
public class IrrigNetModel
{
public int Id { get; set; }
public string Message { get; set; }
public DateTime Date { get; set; }
public string DateText { get; set; }
public int StationId { get; set; }
public string StationName { get; set; }
public float StationLongitude { get; set; }
public float StationLatitude { get; set; }
public int ServiceId { get; set; }
public string ServiceName { get; set; }
}
View:
[XamlCompilation(XamlCompilationOptions.Compile)]
public partial class IrrigNetPage : ContentPage
{
public IrrigNetPage()
{
InitializeComponent();
BindingContext = new IrrigNetViewModel();
}
}
ViewModel
//ServicesModel irrigNetModel = new ServicesModel()
//{
// Id = 1,
// Message = "sample string 2",
// Date = DateTime.Now,
// DateText = "sample string 4",
// StationId = 5,
// StationName = "sample string 6",
// StationLongitude = 1,
// StationLatitude = 1,
// ServiceId = 7,
// ServiceName = "sample string 8"
//};
//public IrrigNetViewModel(ServicesModel services)
//{
// irrigNetModel.Id = services.Id;
// irrigNetModel.Message = services.Message;
// irrigNetModel.Date = services.Date;
// irrigNetModel.DateText = services.DateText;
// irrigNetModel.StationId = services.StationId;
// irrigNetModel.StationName = services.StationName;
// irrigNetModel.StationLongitude = services.StationLongitude;
// irrigNetModel.StationLatitude = services.StationLatitude;
// irrigNetModel.ServiceId = services.ServiceId;
// irrigNetModel.ServiceName = services.ServiceName;
//}
public ObservableCollection<IrrigNetModel> IrrigNetCollection { get; set; } = new ObservableCollection<IrrigNetModel>
{
new IrrigNetModel
{
StationId = 1,
StationName = "Krakatosia",
Message = "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Curabitur scelerisque a lorem sit amet mattis.",
DateText = "21.07.2012."
}
};
public IrrigNetViewModel()
{
IrrigNetService.GetServices("TOKEN", "sr");
TabTappedCommand = new Command((tabName) => OnTapClicked(tabName.ToString()));
HideListOnTapCommand = new Command(HideListOnTap);
IrrigNetModel model = new IrrigNetModel();
//ShowIrrigNetDetailPageCommand = new Command(ShowDetailPage);
var irrigNetModel = new IrrigNetModel
{
//StationName = model.StationName,
//Message = model.Message,
//DateText = model.DateText
StationId = 1,
Message = "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Curabitur scelerisque a lorem sit amet mattis.",
DateText = "03.07.2021."
};
IrrigNetCollection.Add(irrigNetModel);
}
In ViewModel you can see all what I have tried to show data but currnetly it's hardcoded for testing purpose (just to see how my page looks like with some data).
And, of course here is my service:
class IrrigNetService
{
public static async Task<IrrigNetModel> GetServices(string token, string lngCode)
{
string url = DataURL.BASE_URL + "ekonetmobile/getlistnotifications?lngCode={" + lngCode + "}";
IrrigNetModel model = new IrrigNetModel();
try
{
using(var client = new HttpClient())
{
client.DefaultRequestHeaders.TryAddWithoutValidation("Content-Type", "application/json");
client.DefaultRequestHeaders.TryAddWithoutValidation("Authorization", token);
client.DefaultRequestHeaders.TryAddWithoutValidation("Culture", LocalData.Lang);
string content = Newtonsoft.Json.JsonConvert.SerializeObject(model);
HttpResponseMessage result = await client.PostAsync(url, new StringContent(content, Encoding.UTF8, "application/json"));
if (result.StatusCode == System.Net.HttpStatusCode.OK)
{
string resultContent = await result.Content.ReadAsStringAsync();
model = (IrrigNetModel)Newtonsoft.Json.JsonConvert.DeserializeObject(resultContent.ToString(), typeof(IrrigNetModel));
}
else if (result.StatusCode == System.Net.HttpStatusCode.Unauthorized)
{
}
}
}
catch (Exception)
{
model = null;
}
return model;
}
}
POST api/ekonetmobile/getlistnotifications?lngCode={lngCode}
Currently my service show:
{StatusCode: 401, ReasonPhrase: 'Unauthorized', Version: 1.1, Content: System.Net.Http.StreamContent, Headers:
{
Cache-Control: no-cache
Date: Tue, 14 May 2019 12:03:14 GMT
Pragma: no-cache
Server: Microsoft-IIS/8.5
WWW-Authenticate: Bearer
X-Android-Received-Millis: 1557835393828
X-Android-Response-Source: NETWORK 401
X-Android-Selected-Protocol: http/1.1
X-Android-Sent-Millis: 1557835393651
X-AspNet-Version: 4.0.30319
X-Powered-By: ASP.NET
Content-Length: 61
Content-Type: application/json; charset=utf-8
Expires: -1
}}
So, the point is to set value in 'StationName', 'Message', 'DateText' etc, etc from json, instead of "Lorem ipsum dolor sit amet..." and other constant values...
Solution:
I created new class:
public class LocalData
{
public static string Token
{
get
{
return CrossSecureStorage.Current.GetValue("TOKEN");
}
set
{
CrossSecureStorage.Current.SetValue("TOKEN", value);
}
}
}
Then, edited LoginViewModel like this:
class LoginViewModel
{
public string Username { get; set; }
public string Password { get; set; }
public Command LoginCommand => new Command(async () =>
{
LoginModel model = new LoginModel(Username, Password);
if (model.CheckInformation())
{
AccountResponseModel response = await LoginService.Login(model);
if (!string.IsNullOrEmpty(response.Token))
{
await Application.Current.MainPage.DisplayAlert("Prijavljivanje", "Uspešno ste se prijavili", "OK.");
LocalData.Token = response.Token;
Application.Current.MainPage = new AgroNetMasterPage();
}
else
{
await Application.Current.MainPage.DisplayAlert("Prijavljivanje", "Prijava neuspešna. Netačno ime ili lozinka", "OK.");
}
}
else
{
await Application.Current.MainPage.DisplayAlert("Prijavljivanje", "Prijava neuspešna. Netačno ime ili lozinka", "OK.");
}
});
}
As zou can se I set Token in LocalData
LocalData.Token = response.Token;
Also, here is AccountResponseModel:
public class AccountResponseModel
{
public string Token { get; set; }
public string RoleId { get; set; }
public string UserId { get; set; }
}
After all my service now looks little bit diferent:
class IrrigNetService
{
public static async Task<IrrigNetModel> GetServices(string token, string lngCode)
{
IrrigNetModel model = new IrrigNetModel();
try
{
string URL = DataURL.BASE_URL + "agronetmobile/getlistnotifications?lngCode=" + lngCode;
string content = Newtonsoft.Json.JsonConvert.SerializeObject(model);
using (var client = new HttpClient())
{
client.DefaultRequestHeaders.TryAddWithoutValidation("Content-Type", "application/json");
client.DefaultRequestHeaders.TryAddWithoutValidation("Authorization", token);
client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", token);
//client.DefaultRequestHeaders.TryAddWithoutValidation("Culture", LocalData.Lang);
HttpResponseMessage result = await client.PostAsync(URL, new StringContent(content, Encoding.UTF8, "application/json"));
if (result.StatusCode == System.Net.HttpStatusCode.OK)
{
string resultContent = await result.Content.ReadAsStringAsync();
model = (IrrigNetModel)Newtonsoft.Json.JsonConvert.DeserializeObject(resultContent, typeof(IrrigNetModel));
}
else if (result.StatusCode == System.Net.HttpStatusCode.Unauthorized)
{
//
}
}
}
catch (Exception ex)
{
model = null;
}
return model;
}
}
Related
I'm trying to do a JSON post call using a List property (RecurrenceException) but once the AddAppointment() method is called, RecurrenceException will always be null as its supposed to be but I get this exception on my API controller:
Microsoft.Data.SqlClient.SqlException: 'The parameterized query '(#PK int,#Title nvarchar(8),#Description nvarchar(8),#StartDate ' expects the parameter '#RecurrenceException', which was not supplied.'
Below is my client Razor Page code:
async Task AddAppointment(SchedulerCreateEventArgs e)
{
UvwHolidayPlanner holidayPlannerItem = e.Item as UvwHolidayPlanner;
List<DateTime> lst = new List<DateTime>();
holidayPlanner.Pk = holidayPlannerItem.Pk;
holidayPlanner.Title = holidayPlannerItem.Title;
holidayPlanner.Description = holidayPlannerItem.Description;
holidayPlanner.StartDate = holidayPlannerItem.StartDate;
holidayPlanner.EndDate = holidayPlannerItem.EndDate;
holidayPlanner.IsAllDay = holidayPlannerItem.IsAllDay;
if (holidayPlannerItem.RecurrenceRule == null)
{
holidayPlanner.RecurrenceRule = " ";
}
else
{
holidayPlanner.RecurrenceRule = holidayPlannerItem.RecurrenceRule;
}
holidayPlanner.RecurrenceException = holidayPlannerItem.RecurrenceException;
holidayPlanner.RecurrenceId = holidayPlannerItem.RecurrenceId;
await http.CreateClient("ClientSettings").PostAsJsonAsync<UvwHolidayPlanner>($"{_URL}/api/HolidayPlannerOperations/HolidayPlanner", holidayPlanner);
HolidayPlanners = (await http.CreateClient("ClientSettings").GetFromJsonAsync<List<UvwHolidayPlanner>>($"{_URL}/api/lookup/HolidayPlanner"))
.OrderBy(t => t.Title)
.ToList();
StateHasChanged();
}
Below is my class code:
public class UvwHolidayPlanner
{
public string Title { get; set; }
public string Description { get; set; }
public DateTime StartDate { get; set; }
public DateTime EndDate { get; set; }
public bool IsAllDay { get; set; }
public int Pk { get; set; }
public string RecurrenceRule { get; set; }
public List<DateTime> RecurrenceException { get; set; }
public int RecurrenceId { get; set; }
}
And below is my API controller code:
[HttpPost]
[Route("HolidayPlanner")]
public void Post([FromBody] UvwHolidayPlanner item)
{
string SQLSTE = "EXEC [dbo].[usp_AddHolidayPlanner] #PK, #Title, #Description, #StartDate, #EndDate, #IsAllDay, #RecurrenceRule, #RecurrenceException, #RecurrenceId";
using (var context = new TestAppContext())
{
List<SqlParameter> param = new List<SqlParameter>
{
new SqlParameter { ParameterName = "#PK", Value = item.Pk },
new SqlParameter { ParameterName = "#Title", Value = item.Title },
new SqlParameter { ParameterName = "#Description", Value = item.Description },
new SqlParameter { ParameterName = "#StartDate", Value = item.StartDate },
new SqlParameter { ParameterName = "#EndDate", Value = item.EndDate },
new SqlParameter { ParameterName = "#IsAllDay", Value = item.IsAllDay },
new SqlParameter { ParameterName = "#RecurrenceRule", Value = item.RecurrenceRule },
new SqlParameter { ParameterName = "#RecurrenceException", Value = item.RecurrenceException },
new SqlParameter { ParameterName = "#RecurrenceId", Value = item.RecurrenceId }
};
context.Database.ExecuteSqlRaw(SQLSTE, param);
}
}
In our project, we are getting response from wcf service in xml format which we want to deserialize using datacontract serializer.
Below is the xml response.
<ArrayOfCustomerData xmlns="http://schemas.datacontract.org/2004/07/PACRM.QCT">
<CustomerData>
<AccountID>String content</AccountID>
<AccountName1>String content</AccountName1>
</CustomerData>
<CustomerData>
<AccountID>String content</AccountID>
<AccountName1>String content</AccountName1>
</CustomerData>
</ArrayOfCustomerData>
We have written the following DataContract class to deserialize the xml.
[DataContract]
public class ArrayOfCustomerData
{
[DataMember(Name="CustomerData")]
public CustomerData[] customerData { get; set; }
}
[DataContract]
public class CustomerData
{
[DataMember(IsRequired = true, Name = "AccountID")]
public string new_AccountID { get; set; }
[DataMember(IsRequired = true, Name = "AccountName1")]
public string new_accountname1 { get; set; }
}
C# code for deserialization is given below.
DataContractSerializer dcs = new DataContractSerializer(typeof(ArrayOfCustomerData));
ArrayOfCustomerData data=new ArrayOfCustomerData();
using (var stream = new StreamReader(response.GetResponseStream()))
{
var text=stream.ReadToEnd();
MemoryStream ms = new MemoryStream(Encoding.UTF8.GetBytes(text));
XmlDictionaryWriter xdw = XmlDictionaryWriter.CreateTextWriter(ms, Encoding.UTF8);
dcs.WriteObject(xdw, data);
}
when i check the data.cusotmerData, it is returning null.
Can anyone please provide solution for this issue? Thanks!
You don't need the class ArrayOfCustomerData - that is just adding an unecessary extra element to the expected XML. You can use CustomerData[], as the type passed to the DataContractSerializer constructor, as shown below:
public class StackOverflow_24673714
{
const string XML = #"<ArrayOfCustomerData xmlns=""http://schemas.datacontract.org/2004/07/PACRM.QCT"">
<CustomerData>
<AccountID>String content ID 1</AccountID>
<AccountName1>String content name 1</AccountName1>
</CustomerData>
<CustomerData>
<AccountID>String content ID 2</AccountID>
<AccountName1>String content name 2</AccountName1>
</CustomerData>
</ArrayOfCustomerData>";
[DataContract(Name = "CustomerData", Namespace = "http://schemas.datacontract.org/2004/07/PACRM.QCT")]
public class CustomerData
{
[DataMember(IsRequired = true, Name = "AccountID")]
public string new_AccountID { get; set; }
[DataMember(IsRequired = true, Name = "AccountName1")]
public string new_accountname1 { get; set; }
}
public static void Test()
{
var ms = new MemoryStream();
var ws = new XmlWriterSettings { Indent = true, IndentChars = " ", OmitXmlDeclaration = true, Encoding = Encoding.UTF8 };
var w = XmlWriter.Create(ms, ws);
var dcs = new DataContractSerializer(typeof(CustomerData[]));
var obj = new CustomerData[] {
new CustomerData { new_AccountID = "String content 1", new_accountname1 = "String content 2" },
new CustomerData { new_AccountID = "String content 3", new_accountname1 = "String content 4" }
};
dcs.WriteObject(w, obj);
w.Flush();
Console.WriteLine(Encoding.UTF8.GetString(ms.ToArray()));
ms = new MemoryStream(Encoding.UTF8.GetBytes(XML));
var cds = (CustomerData[])dcs.ReadObject(ms);
Console.WriteLine(cds.Length);
foreach (var cd in cds)
{
Console.WriteLine(" {0} - {1}", cd.new_AccountID, cd.new_accountname1);
}
}
}
In debugging the issue in this thread: InvalidCastException when querying nested collection with LINQ I found out that something is wrong with how my Category EntitySet is populated. After selecteding a Category and throwing this exception to see what's going on I get this:
throw new Exception("CID: " + cat.CategoryID +
" LCID: " + cat.LocalizedCategories.First().LocalizedCategoryID +
" CID from LC: " + cat.LocalizedCategories.First().Category.CategoryID);
CID: 352 LCID: 352 CID from LC: 191
What am I doing wrong that causes CategoryID to have different values depending on how I LINQ to it? It should be 191, and not the same value as the LocalizedCategoryID.
This is the code I use to get the Category:
int categoryId = 352; // In reality this comes from a parameter and is supposed
// to be 191 to get the Category.
var cat = categoriesRepository.Categories.First(c => c.CategoryID == categoryId);
This is my domain object with some unrelated stuff stripped:
[Table(Name = "products")]
public class Product
{
[HiddenInput(DisplayValue = false)]
[Column(Name = "id", IsPrimaryKey = true, IsDbGenerated = true, AutoSync = AutoSync.OnInsert)]
public int ProductID { get; set; }
[Required(ErrorMessage = "Please enter a product name")]
[Column]
public string Name { get; set; }
[Required(ErrorMessage = "Please enter a description")]
[DataType(DataType.MultilineText)]
[Column(Name = "info")]
public string Description { get; set; }
private EntitySet<Category> _Categories = new EntitySet<Category>();
[System.Data.Linq.Mapping.Association(Storage = "_Categories", OtherKey = "CategoryID")]
public ICollection<Category> Categories
{
get { return _Categories; }
set { _Categories.Assign(value); }
}
}
[Table(Name = "products_types")]
public class Category
{
[HiddenInput(DisplayValue = false)]
[Column(Name = "id", IsPrimaryKey = true, IsDbGenerated = true, AutoSync = AutoSync.OnInsert)]
public int CategoryID { get; set; }
public string NameByCountryId(int countryId)
{
return _LocalizedCategories.Single(lc => lc.CountryID == countryId).Name;
}
private EntitySet<LocalizedCategory> _LocalizedCategories = new EntitySet<LocalizedCategory>();
[System.Data.Linq.Mapping.Association(Storage = "_LocalizedCategories", OtherKey = "LocalizedCategoryID")]
public ICollection<LocalizedCategory> LocalizedCategories
{
get { return _LocalizedCategories; }
set { _LocalizedCategories.Assign(value); }
}
private EntitySet<Product> _Products = new EntitySet<Product>();
[System.Data.Linq.Mapping.Association(Storage = "_Products", OtherKey = "ProductID")]
public ICollection<Product> Products
{
get { return _Products; }
set { _Products.Assign(value); }
}
}
[Table(Name = "products_types_localized")]
public class LocalizedCategory
{
[HiddenInput(DisplayValue = false)]
[Column(Name = "id", IsPrimaryKey = true, IsDbGenerated = true, AutoSync = AutoSync.OnInsert)]
public int LocalizedCategoryID { get; set; }
[Column(Name = "products_types_id")]
private int CategoryID;
private EntityRef<Category> _Category = new EntityRef<Category>();
[System.Data.Linq.Mapping.Association(Storage = "_Category", ThisKey = "CategoryID")]
public Category Category
{
get { return _Category.Entity; }
set { _Category.Entity = value; }
}
[Column(Name = "country_id")]
public int CountryID { get; set; }
[Column]
public string Name { get; set; }
}
This (in class Category) looks weird:
[System.Data.Linq.Mapping.Association(Storage = "_LocalizedCategories",
OtherKey = "LocalizedCategoryID" )] // ????
public ICollection<LocalizedCategory> LocalizedCategories
Category has a collection of LocalizedCategorys, which means that in the database the table products_types_localized has a foreign keyCategoryID. That field should be the "OtherKey". How was this mapping generated?
I have created a custom remote data annotation attribute called remoteVal in MVC 2 as below
public class remoteValAttribute:ValidationAttribute
{
public string Action { get; set; }
public string Controller { get; set; }
public string ParameterName { get; set; }
public string RouteName { get; set; }
public override bool IsValid(object value)
{
return true;
}
}
Adapter class
public class RemoteAttributeAdapter:DataAnnotationsModelValidator<remotevalAttribute>
{
public RemoteAttributeAdapter(ModelMetadata metadata, ControllerContext context, remoteVal attribute) : base(metadata, context, attribute) { }
public override IEnumerable<ModelClientValidationRule> GetClientValidationRules()
{
ModelClientValidationRule rule = new ModelClientValidationRule()
{
ErrorMessage = ErrorMessage,
ValidationType = "remoteVal"
};
rule.ValidationParameters["url"] = GetUrl();
rule.ValidationParameters["parameterName"] = Attribute.ParameterName;
return new ModelClientValidationRule[] { rule };
}
private string GetUrl()
{
RouteValueDictionary rvd = new RouteValueDictionary(){
{"controller",Attribute.Controller},
{"action",Attribute.Action}
};
var virtualPath = RouteTable.Routes.GetVirtualPath(ControllerContext.RequestContext, Attribute.RouteName, rvd);
if (virtualPath == null)
{
throw new InvalidOperationException("No route matched!");
}
return virtualPath.VirtualPath;
}
}
Added the attribute in the model class
[Required(ErrorMessage = "Title Required")]
[remoteVal(Controller = "Validation", Action = "IsTitle_Available", ParameterName = "Title")]
[ScaffoldColumn(false)]
public object Title { get; set; }
Added the jQuery script as below
<script type="text/javascript" src="../../Scripts/jquery-1.4.1.min.js"></script>
<script type="text/javascript" src="../../Scripts/jquery.validate.js"></script>
<script type="text/javascript" src="../../Scripts/MicrosoftMvcJQueryValidation.js"></script>
<script type="text/javascript">
$(function () {
var ermessage = '';
jQuery.validator.addMethod("remoteVal", function (value, element, params) {
var validator = this;
var valid = false;
if (this.optional(element)) {
return true;
}
else {
var url = params.url;
var parameterName = params.parameterName;
var newUrl = ((url.indexOf('?') < 0) ? (url + '?') : (url + '&'))
+ encodeURIComponent(parameterName) + '=' + encodeURIComponent(value);
var response = $.ajax({
url: newUrl,
async: false
}).responseText;
if (response == 'OK')
valid = true;
else {
valid = false;
var errors = {};
errors[element.name] = response;
validator.showErrors(errors);
}
}
return valid;
});
});
</script>
If the Title is already registered, Response from AJAX call is to be shown as the error message. But the error is always shown as The field Title is invalid. Please help me in resolving this issue.
I wonder if anyone can shed some light on this problem..
I've got an option group drop-down for selecting a person's ethnicity – however it’s not storing the value in the model.
ViewModel
[UIHint("EthnicOriginEditorTemplate")]
[DisplayName("Question 6: Ethnic Origin")]
public int EthnicOrigin { get; set; }
Helper : GroupDropList.Cs
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Web.Mvc;
using System.Web.Routing;
namespace Public.Helpers
{
public static class GroupDropListExtensions
{
public static string GroupDropList(this HtmlHelper helper, string name, IEnumerable<GroupDropListItem> data, int SelectedValue, object htmlAttributes)
{
if (data == null && helper.ViewData != null)
data = helper.ViewData.Eval(name) as IEnumerable<GroupDropListItem>;
if (data == null) return string.Empty;
var select = new TagBuilder("select");
if (htmlAttributes != null)
select.MergeAttributes(new RouteValueDictionary(htmlAttributes));
select.GenerateId(name);
var optgroupHtml = new StringBuilder();
var groups = data.ToList();
foreach (var group in data)
{
var groupTag = new TagBuilder("optgroup");
groupTag.Attributes.Add("label", helper.Encode(group.Name));
var optHtml = new StringBuilder();
foreach (var item in group.Items)
{
var option = new TagBuilder("option");
option.Attributes.Add("value", helper.Encode(item.Value));
if (SelectedValue != 0 && item.Value == SelectedValue)
option.Attributes.Add("selected", "selected");
option.InnerHtml = helper.Encode(item.Text);
optHtml.AppendLine(option.ToString(TagRenderMode.Normal));
}
groupTag.InnerHtml = optHtml.ToString();
optgroupHtml.AppendLine(groupTag.ToString(TagRenderMode.Normal));
}
select.InnerHtml = optgroupHtml.ToString();
return select.ToString(TagRenderMode.Normal);
}
}
public class GroupDropListItem
{
public string Name { get; set; }
public List<OptionItem> Items { get; set; }
}
public class OptionItem
{
public string Text { get; set; }
public int Value { get; set; }
}
}
This is my EditorTemplate
<%# Import Namespace="Public.Helpers"%>
<%# Control Language="C#" Inherits="System.Web.Mvc.ViewUserControl<int>"%>
<%=Html.GroupDropList("EthnicOrigin",
new[]
{
new GroupDropListItem
{
Name = "Ethnicity",
Items = new List<OptionItem>
{
new OptionItem {Value = 0, Text = "Please Select"}
}
},
new GroupDropListItem
{
Name = "a) White",
Items = new List<OptionItem>
{
new OptionItem {Value = 1, Text = "British"},
new OptionItem {Value = 2, Text = "Irish"},
new OptionItem {Value = 3, Text = "Other White (Please specify below)"}
}
},
--snip
}, Model, null)%>
And in the view I'm referencing it as:
<%=Html.EditorFor(x => x.EthnicOrigin, "EthnicOriginEditorTemplate")%>
However it's not passing through the selected Value into the model... has anyone experienced similar problems... many thanks in advance for some pointers.
Your select doesn't have a name attribute and so when you submit the form the selected value is not sent to the server. You need to add a name:
select.GenerateId(name);
select.MergeAttribute("name", name);
Just changed the helper class to get it work for MVC 3 and with nullable int.
Thanks a lot for the class, saves me plenty of time.
public static class GroupDropListExtensions
{
public static MvcHtmlString GroupDropList(this HtmlHelper helper, string name, IEnumerable<GroupDropListItem> data, int? SelectedValue, object htmlAttributes)
{
if (data == null && helper.ViewData != null)
data = helper.ViewData.Eval(name) as IEnumerable<GroupDropListItem>;
if (data == null) return new MvcHtmlString(string.Empty);
var select = new TagBuilder("select");
if (htmlAttributes != null)
select.MergeAttributes(new RouteValueDictionary(htmlAttributes));
select.GenerateId(name);
select.MergeAttribute("name", name);
var optgroupHtml = new StringBuilder();
var groups = data.ToList();
foreach (var group in data)
{
var groupTag = new TagBuilder("optgroup");
groupTag.Attributes.Add("label", helper.Encode(group.Name));
var optHtml = new StringBuilder();
foreach (var item in group.Items)
{
var option = new TagBuilder("option");
option.Attributes.Add("value", helper.Encode(item.Value));
if (SelectedValue != 0 && item.Value == SelectedValue)
option.Attributes.Add("selected", "selected");
option.InnerHtml = helper.Encode(item.Text);
optHtml.AppendLine(option.ToString(TagRenderMode.Normal));
}
groupTag.InnerHtml = optHtml.ToString();
optgroupHtml.AppendLine(groupTag.ToString(TagRenderMode.Normal));
}
select.InnerHtml = optgroupHtml.ToString();
return new MvcHtmlString(select.ToString(TagRenderMode.Normal));
}
}
public class GroupDropListItem
{
public string Name { get; set; }
public List<OptionItem> Items { get; set; }
}
public class OptionItem
{
public string Text { get; set; }
public int Value { get; set; }
}
This is supported natively using SelectListGroup as of ASP.NET MVC 5.2:
var items = new List<SelectListItem>();
var group1 = new SelectListGroup() { Name = "Group 1" };
items.Add(new SelectListItem() { Text = "Item1", Group = group1 });
Then in MVC, do
#Html.DropDownList("select", items)