I have used the following code to use FB Like on my website...but clicking on like posts this to FB "Jonny likes http://www.site.com" but not the actual url of the page which was liked i.e "www.site.com/reports/1".
I have placed this code in the master file...
<div id="fb-root"></div>
<script>(function(d, s, id) {
var js, fjs = d.getElementsByTagName(s)[0];
if (d.getElementById(id)) return;
js = d.createElement(s); js.id = id;
js.src = "//connect.facebook.net/en_US/all.js#xfbml=1";
fjs.parentNode.insertBefore(js, fjs);
}(document, 'script', 'facebook-jssdk'));</script>
and this in the respective pages....
<div class="fb-like" data-href="http://citizen.tricedeals.com" data-send="true" data-width="450" data-show-faces="false" data-font="verdana"></div>
You must implement the following Meta Tags information while doing
Like press...
og:title
og:description
og:url
og:image
Code Behind
public class MetaTag
{
public string PageURL { get; set; }
public string TagName { get; set; }
public string MetaTagContent { get; set; }
public string SiteName { get; set; }
}
var fbTitleTag = new MetaTag
{
PageURL = "/",
MetaTagName = "og:title",
SiteName = "Your Site Name",
MetaTagContent = "Your Title"
};
var fbDesc = new MetaTag
{
PageURL = "/",
MetaTagName = "og:description",
SiteName = "Site Name",
MetaTagContent = "Your Description"
};
var fbUrl = new MetaTag
{
PageURL = "/",
MetaTagName = "og:url",
SiteName = "Site Name",
MetaTagContent = "URL"
};
var fbImage = new MetaTag
{
PageURL = "/",
MetaTagName = "og:image",
SiteName = "Site Name",
MetaTagContent = "Image URL"
};
System.Collections.Generic.List<MetaTag> List = new System.Collections.Generic.List<MetaTag>();
List.Add(fbTitleTag);
List.Add(fbDesc);
List.Add(fbUrl);
List.Add(fbImage);
RenderMetaTags(List, "SiteName", strRawUrl, ltMetaTags);
Here ltMetaTags is the Literal control to place in Master page. See bottom of the asnwer.
public static void RenderMetaTags(List<MetaTag> MetaTags, string sitename, string strRawURL, Literal ltlMetaHolders)
{
// ltlMetaHolders.Text = "";
foreach (MetaTag oAgentMetaTag in MetaTags)
{
RenderMetaTagByContentName(ltlMetaHolders, oAgentMetaTag.MetaTagName, oAgentMetaTag.MetaTagContent);
}
}
public static void RenderMetaTagByContentName(Literal ltlMetaHolder, string contentName, string content, bool isProp)
{
var metaTagFromat = isProp ? "<meta property=\"{0}\" content=\"{1}\" />" : "<meta name=\"{0}\" content=\"{1}\" /> ";
ltlMetaHolder.Text += string.Format(metaTagFromat, contentName, content);
}
HTML in Master Page
Following is the Literal in Head tag of Master Page
<asp:Literal ID="ltMetaTags" Mode="Transform" runat="server"></asp:Literal>
Related
I am trying to create EmailTemplates. I have been able to write the controller codes and also created a .txt file in a folder but when the mail is been sent, the users receive it as a HTML code.
Controller code
public async Task<ActionResult> Register(RegisterViewModel model)
{
if (ModelState.IsValid)
{
var user = new ApplicationUser { UserName = model.Email, Email = model.Email,
Firstname = model.Firstname, Surname = model.Surname, Gender = model.Gender};
var result = await UserManager.CreateAsync(user, model.Password);
if (result.Succeeded)
{
var code = await UserManager.GenerateEmailConfirmationTokenAsync(user.Id);
var callbackUrl = Url.Action("ConfirmEmail", "Account", new { userId = user.Id, code = code }, protocol: Request.Url.Scheme);
string body = string.Empty;
var root = AppDomain.CurrentDomain.BaseDirectory; using (var reader = new System.IO.StreamReader(root + #"/EmailTemplates/ConfirmAccount.txt"))
{
string readFile = reader.ReadToEnd();
string StrContent = string.Empty;
StrContent = readFile;
//Assing the field values in the template
StrContent = StrContent.Replace("[Firstname]", user.Firstname);
StrContent = StrContent.Replace("[Surname]", user.Surname);
StrContent = StrContent.Replace("[Code]", callbackUrl);
StrContent = StrContent.Replace("[Year]", DateTime.Now.Year.ToString());
body = StrContent.ToString();
}
await UserManager.SendEmailAsync(user.Id, "Confirm Your Account", body);
return View("DisplayEmail");
}
AddErrors(result);
}
return View(model);
}
Below is the .txt file content
<!doctype html>
<html lang="tr">
<head>
<meta charset="utf-8">
</head>
<body>
<p>Dear [Firstname] [Surname],</p>
<p>Kindly Confirm your Email by clicking the link below</p>
<p>[Code]</p>
</body>
The output is in form of a HTML.
The better way to send HTML formatted Email your code will be in "ConfirmAccount.htm"
<!doctype html>
<html lang="tr">
<head>
<meta charset="utf-8">
</head>
<body>
<p>Dear [Firstname] [Surname],</p>
<p>Kindly Confirm your Email by clicking the link below</p>
<p>[Code]</p>
</body>
Read HTML file Using System.IO.File.ReadAllText. get all HTML code in string variable.
string Body = System.IO.File.ReadAllText(HttpContext.Current.Server.MapPath("EmailTemplates/ConfirmAccount.htm"));
Replace a Particular string with your custom value.
Body = Body.Replace("[Firstname]", user.Firstname);
Call SendEmail(string Body) Function and do a procedure to send an email.
Replace the Session email and Configuration app settings with your ones.
public static void SendEmail(string Body)
{
MailMessage message = new MailMessage();
message.From = new MailAddress(Session["Email"].Tostring());
message.To.Add(ConfigurationSettings.AppSettings["RequesEmail"].ToString());
message.Subject = "Request from " + SessionFactory.CurrentCompany.CompanyName + " to add a new supplier";
message.IsBodyHtml = true;
message.Body = Body;
SmtpClient smtpClient = new SmtpClient();
smtpClient.UseDefaultCredentials = true;
smtpClient.Host = ConfigurationSettings.AppSettings["SMTP"].ToString();
smtpClient.Port = Convert.ToInt32(ConfigurationSettings.AppSettings["PORT"].ToString());
smtpClient.EnableSsl = true;
smtpClient.Credentials = new System.Net.NetworkCredential(ConfigurationSettings.AppSettings["USERNAME"].ToString(), ConfigurationSettings.AppSettings["PASSWORD"].ToString());
smtpClient.Send(message);
}
You can check out in message.IsBodyHtml = true SendEmailAsyn Method
I am trying to add multiple png images as inline attachments to email body. My email body only has the last image. Looks like the memorysteam was overwritten new one. I tried to use AlternateView as this post suggested How to attach multi images in email body in windows application?. But it does not show any image. How to add multiple images attachments? Thanks.
struct Webpage
{
public string Id { get; set; }
public Byte[] Img { get; set; }
public string SiteName { get; set; }
public DateTime CollectTime { get; set; }
}
//
static void SendMultileImgsWEmail(List<Webpage> msg)
{
MailMessage mailMessage = new MailMessage();
SmtpClient client = new SmtpClient(mailserver);
mailMessage.IsBodyHtml = true;
mailMessage.From = new MailAddress(From);
mailMessage.To.Add(new MailAddress(To));
mailMessage.Subject = "xxx";
foreach (Webpage item in msg)
{
byte[] image = item.Img;
Attachment att = new Attachment(new MemoryStream(item.Img), item.SiteName);
att.ContentDisposition.Inline = true;
att.ContentId = item.Id;
att.ContentType.MediaType = "image/png";
mailMessage.Body += "Website Name: " + item.SiteName + Environment.NewLine + Environment.NewLine;
mailMessage.Body += "Screenshot Time: " + item.CollectTime + Environment.NewLine + Environment.NewLine;
mailMessage.Body = String.Format( #"<img src=""cid:{0}"" />", att.ContentId);
mailMessage.Attachments.Add(att);
}
//send message
try
{
client.Send(mailMessage);
}
catch (Exception ex)
{
throw;
}
}
I want to fill data in excel and download that excel..
Following is the code..
public void DownloadExcel(int acid, int GroupId)
{
// Working Code
#region DownloadExcel
// string sConnectionString = ConfigurationManager.ConnectionStrings["TrainingMVCContext"].ConnectionString;
string sConnectionString = string.Empty;
LoginUserDetails objLoginUserDetails = (LoginUserDetails)InsiderTrading.Common.Common.GetSessionValue((string)ConstEnum.SessionValue.UserDetails);
sConnectionString = objLoginUserDetails.CompanyDBConnectionString;
SqlConnection con = new SqlConnection(sConnectionString);
SqlCommand cmd = new SqlCommand();
con.Open();
DataTable dt = new DataTable();
cmd = new SqlCommand("st_tra_NSEDownloadGroupWiseExcel", con);
cmd.Parameters.AddWithValue("#GroupId", GroupId);
cmd.CommandType = CommandType.StoredProcedure;
SqlDataAdapter adp = new SqlDataAdapter(cmd);
// ds = new DataSet();
adp.Fill(dt);
Microsoft.Office.Interop.Excel.Range oRng;
Microsoft.Office.Interop.Excel.Workbook mWorkBook;
Microsoft.Office.Interop.Excel.Sheets mWorkSheets;
Microsoft.Office.Interop.Excel.Worksheet mWSheet1;
Microsoft.Office.Interop.Excel.Application oXL;
object misValue = System.Reflection.Missing.Value;
string directory = ConfigurationManager.AppSettings["Document"];
string path = "Z:\\For Excel Demo\\Application\\InsiderTrading\\Document" + "\\" + "Stock Exchange Submission.xlsx";
oXL = new Microsoft.Office.Interop.Excel.Application();
oXL.Visible = true;
oXL.DisplayAlerts = false;
mWorkBook = oXL.Workbooks.Open(path, 0, false, 5, "", "", false, Microsoft.Office.Interop.Excel.XlPlatform.xlWindows, "", true, false, 0, true, false, false);
mWorkSheets = mWorkBook.Worksheets;
mWSheet1 = (Microsoft.Office.Interop.Excel.Worksheet)mWorkSheets.get_Item("Sheet1");
Microsoft.Office.Interop.Excel.Range range = mWSheet1.UsedRange;
for (var row = 4; row <= dt.Rows.Count; row++)
{
for (var col = 0; col < dt.Columns.Count; col++)
{
mWSheet1.Cells[row + 1, col + 1].Value = dt.Rows[row - 1][col];
}
}
string Filename = "Testing.xlsx";
string pathTosave = (Path.Combine(directory, Filename));
mWorkBook.SaveAs(pathTosave);
using (var memoryStream = new MemoryStream())
{
HttpContext.Response.Clear();
HttpContext.Response.Charset = "";
HttpContext.Response.ContentType = "application/vnd.ms-excel";
HttpContext.Response.AddHeader("Content-Disposition", "inline;filename=" + pathTosave);
System.Text.StringBuilder strHTMLContent = new System.Text.StringBuilder();
//strHTMLContent.Append(LetterHTMLContent);
HttpContext.Response.Write(strHTMLContent);
HttpContext.Response.End();
HttpContext.Response.Flush();
}
mWorkBook.Close();
mWSheet1 = null;
mWorkBook = null;
oXL.Quit();
GC.WaitForPendingFinalizers();
GC.Collect();
GC.WaitForPendingFinalizers();
GC.Collect();
#endregion DownloadExcel
}
but it gives the error For line
mWorkBook = oXL.Workbooks.Open(path, 0, false, 5, "", "", false, Microsoft.Office.Interop.Excel.XlPlatform.xlWindows, "", true, false, 0, true, false, false);
error msg as follows--
An exception of type 'System.Runtime.InteropServices.COMException' occurred in InsiderTrading.dll but was not handled in user code
How can i do that..Plz help
Why dont you use a library, "LinqToExcell" to do all the plumbing?
https://www.codeproject.com/Articles/659643/Csharp-Query-Excel-and-CSV-Files-Using-LinqToExcel
Just upload the file using the following code in your controller. Note, you will need to save the file in your application because browsers do not allow you to access the location of your uploaded file (security purposes):
#region ImportExcell
public ActionResult ImportExcel()
{
return View();
}
[HttpPost]
public ActionResult ImportExcel(HttpPostedFileBase upload, FormCollection fc)
{
var v = System.Web.HttpContext.Current.Request.Files["upload"];
string contentType = upload.ContentType;
FileInfo fInfo = new FileInfo(upload.FileName);
//I am only allowing this kind of excel file. I got this from HttpPostedFileBase during debuging and checking content
if (!contentType.Contains(#"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"))
{
ModelState.AddModelError("", string.Format("This is not a valid file. This is '{0}'", contentType));
return View();
}
var fileName = upload.FileName;
//This is my temporary saving spot in my application
var filePathSaveTo = Server.MapPath(#"/Upload/ExcelFile");
string savedFileName = Path.Combine(filePathSaveTo, fileName);
//Now we need to save the file in a temp spot so we can access it later
upload.SaveAs(savedFileName);
try
{
string returnMsg = _fileDocDAL.LoadFromExcel(savedFileName);
ModelState.AddModelError("", string.Format("Done! " + returnMsg));
}
catch (Exception e)
{
string error = AliKuli.Utilities.ExceptionNS.ErrorMsgClass.GetInnerException(e);
ModelState.AddModelError("", string.Format(error));
}
return View();
}
#endregion
And your view....
#model ModelsClassLibrary.Models.Documents.FilesNS.FilesDocImportVM
#using (Html.BeginForm("ImportExcel", "FileDocs", null, FormMethod.Post, new { enctype = "multipart/form-data" }))
{
#Html.AntiForgeryToken()
<div class="form-horizontal">
#Html.ValidationSummary(true, "", new { #class = "text-danger" })
<div class="panel panel-info">
<div class="panel-heading">
Required Fields
</div>
<div class="panel-body">
<div class="form-group">
#Html.Label("Excel File", new { #class = "control-label col-md-2" })
<div class="col-md-10">
<input type="file" multiple="multiple" name="upload" id="front" />
</div>
</div>
<div class="form-group">
<div class="col-md-10">
<input type="submit" value="Upload" class="btn btn-success" /> |
#Html.ActionLink("Back to List", "Index")
</div>
</div>
</div>
</div>
</div>
}
My model is as below, but you need to make your model just like the data in the excel sheet. This is all explained in the documentation for LinqToExcell
namespace ModelsClassLibrary.Models.Documents.FilesNS
{
//[NotMapped]
public class FilesDocImportVM
{
public FilesDocImportVM()
{
FileId = -1;
}
#region Files
/// <summary>
/// The file number's ID
/// </summary>
public int FileId { get; set; }
/// <summary>
/// This is the file number that the user sees.
/// </summary>
public string FileNo { get; set; }
/// <summary>
/// This is the name of the file
/// </summary>
public string Description { get; set; }
/// <summary>
/// This is the parent's ID. 0 means no ID
/// </summary>
public int ParentId { get; set; }
#endregion
public int GetFileNumberFromOldFileNumber()
{
if (FileNo.IsNullOrEmpty() || FileId == -1 || Description.IsNullOrEmpty())
throw new Exception(string.Format("Proper Data not received. Record is {0}.FilesDocImportVM.GetFileNumberFromOldFileNumber ", this.ToString()));
return new FileDoc().GetFileNumberFromOldFileNumber(FileNo);
}
public void SelfErrorCheck()
{
if(FileId == -1 || FileNo.IsNullOrEmpty() || Description.IsNullOrEmpty())
throw new Exception(string.Format("Proper Data not received. Record is {0}. FilesDocImportVM.SelfErrorCheck", this.ToString()));
}
#region Category
/// <summary>
/// This is the categories ID
/// </summary>
public int CategoryId { get; set; }
/// <summary>
/// This is the name of the category
/// </summary>
public string CategoryName { get; set; }
#endregion
public override string ToString()
{
return string.Format("FileId: {0}, FileNo: {1}, Description: {2}, ParentId: {3}, CategoryId: {4}, CategoryName: {5}",
FileId,
FileNo,
Description,
ParentId,
CategoryId,
CategoryName);
}
}
}
The uploading code is pretty simple... this is the code that is used
public string LoadFromExcel(string excelFileName)
{
int noOfFiles = 0;
//*** NOTE - The line below is the code to import from Excel. I have wrapped it a bit to make it simple... the wrapper is below. The code after this line is just for fixing and checking the data.
var excelFile = AliKuli.Utilities.ExcellUtility.ImportFromExcelWithHeader(excelFileName, "AliKuliFiles");
if (excelFile.IsNullOrEmpty())
throw new Exception(string.Format("Utility Class failed to load. FileDocsDAL.LoadFromExcel"));
string totalFilesMsg = string.Format("Total Orignal Files: {0}", excelFile.Count());
var dataArray = excelFile.OrderBy(x => x.FileId).ToList();
if (dataArray.IsNullOrEmpty())
throw new Exception(string.Format("Data array failed to load. FileDocsDAL.LoadFromExcel"));
CheckTheData(dataArray);
//first make the categories
User theUser = Get_User();
string temp = "";
if (noOfFiles == 324)
temp = "Found";
CreateCategories(dataArray, theUser);
CreateAndSaveFile(ref noOfFiles, dataArray, theUser);
//now add the parents
SaveTheParents();
totalFilesMsg += " Counted: " + noOfFiles;
//CreateFileWithCategory(dataArray, admin);
return totalFilesMsg;
}
This is the wrapper code I wrote to wrap the LinqToExcel
public static class ExcellUtility
{
/// <summary>
/// This will read in the excel file such that it will stringify the cols of each row.
/// Example. If there are 3 cols, the the first 3 entries will be for col 0, then 1, then Col 2,
/// then... the 4th entry will again be col1
/// </summary>
/// <param name="fileName"></param>
/// <returns></returns>
public static List<FilesDocImportVM> ImportFromExcelWithHeader(string excelFileName, string sheetName)
{
ExcelUtilityClass euc;
ExcelQueryFactory excel;
MakeExcelUtilityClass(excelFileName, out euc, out excel);
//This creates a IQueriable<FilesDocImportVM>
var data = from c in excel.Worksheet<FilesDocImportVM>(sheetName) select c;
var colNames = excel.GetColumnNames(sheetName).ToArray();
var datalist = data.ToList();
return datalist;
}
private static void MakeExcelUtilityClass(string excelFileName, out ExcelUtilityClass euc, out ExcelQueryFactory excel)
{
if (excelFileName.IsNullOrEmpty())
throw new Exception("No Excel File Name Passed");
euc = new ExcelUtilityClass();
excel = new ExcelQueryFactory(excelFileName);
}
}
}
Looks more complicated than it is... the uploading part was easy - just about one line of code after writing a the wrapper.
Good luck.
I have a view, where I'm calling an ActionResult method, but putting a breakpoint in the method tells me it's not being called.
<div>
<ul class="list-group">
#foreach (var item in Model)
{
<li class="list-group-item">
<h4>Slide ID: #item.SlideId</h4>
<p><i>Received: #item.TimeStamp</i></p>
<div class="row">
<div class="col-md-4">
<h4>#Html.ActionLink("View details", "Well", new {slideid = item.SlideId})</h4>
<img src="#Url.Action("Index", "Images", new {id = item.SlideId})"/> //This is where I want to call the method
</div>
</div>
</li>
}
</ul>
And here's the method:
public class ImagesController : Controller
{
// GET: Images
public ActionResult Index(string id)
{
byte[] imageData = new byte[0];
string cs = "Data Source=" + "some path";
using (SQLiteConnection con = new SQLiteConnection(cs))
{
string stm = "SELECT LastImage FROM Well WHERE SlideId = " + "'" + id + "'";
con.Open();
using (SQLiteCommand cmd = new SQLiteCommand(stm, con))
{
using (SQLiteDataReader rdr = cmd.ExecuteReader())
{
while (rdr.Read())
{
imageData = Serialize(rdr["LastImage"]);
}
rdr.Close();
}
}
con.Close();
}
return File(imageData, "image/png");
}
public static byte[] Serialize(object obj)
{
var binaryFormatter = new BinaryFormatter();
var ms = new MemoryStream();
binaryFormatter.Serialize(ms, obj);
return ms.ToArray();
}
}
What I'm trying to achieve with this code is to load in an image from the database into the view. Any hints as to what I'm doing wrong?
Now with RouteConfig:
public class RouteConfig
{
public static void RegisterRoutes(RouteCollection routes)
{
routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
routes.MapRoute(
name: "Default",
url: "{controller}/{action}/{id}",
defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional }
);
}
}
When you write <img src="#Url.Action("Index", "Images", new {id = item.SlideId})"/> you don't call the action but the route url. The result is a string, for example localhost:8080/images/index/abcd123456 so, if you want to call the action, you need to use #Html.Action("Index", "Images", new {id = item.SlideId}). Note #Html.Action instead of #Url.Action
I think instead of opening and closing a db connection for each image, a better approach would be to gather all the information to render that page and send it in the model of the view you posted. Say it's called Index action of HomeController. It would look like something like this:
public class HomeController : Controller
{
public ActionResult Index(string id)
{
var listOfItems = new List<SomeClass>();
string cs = "Data Source=" + "some path";
using (SQLiteConnection con = new SQLiteConnection(cs))
{
string stm = "SELECT SlideId, TimeStamp, LastImage FROM Well";
con.Open();
using (SQLiteCommand cmd = new SQLiteCommand(stm, con))
{
using (SQLiteDataReader rdr = cmd.ExecuteReader())
{
while (rdr.Read())
{
var someItem = new SomeClass()
{
SlideId = rdr["SlideId"],
ImageData = Serialize(rdr["LastImage"]),
TimeStamp = rdr["TimeStamp"]
};
listOfItems.Add(someItem);
}
rdr.Close();
}
}
con.Close();
}
return View(listOfItems);
}
}
Of course if there are too many items you should always consider paging and limit the number of items in the list to cut down the response times.
I populate Dropdownlist from database and I want to show certain elements in red by setting their "class" attribute. So, I tried lots of ways and later I have created a custom HTML Helper for Dropdownlist. But it does not make any sense and although it seems to be added class attribute, this paramater cannot pass to Razor View from Controller. Could you help pls?
MyHelper.cs:
public static MvcHtmlString Custom_DropdownList(this HtmlHelper helper, string name, IEnumerable<SelectListItem> list, object htmlAttributes)
{
TagBuilder dropdown = new TagBuilder("select");
dropdown.Attributes.Add("name", name);
dropdown.Attributes.Add("id", name);
StringBuilder options = new StringBuilder();
foreach (var item in list)
{
options = options.Append("<option value='" + item.Value + "'>" + item.Text + "</option>");
}
dropdown.InnerHtml = options.ToString();
dropdown.MergeAttributes(new RouteValueDictionary(htmlAttributes));
return MvcHtmlString.Create(dropdown.ToString(TagRenderMode.Normal));
}
Controller:
private void PopulateMeetingsDropDownList(object selectedMeetings = null)
{
var meetingsQuery = repository.Meetings
.Join(repository.Cities, m => m.MeetingCityId, c => c.CityID,
(m, c) => new
{
CityID = c.CityID,
CityName = c.CityName,
MeetingDate = m.MeetingStartDate
}
)
.OrderBy(x => x.CityID)
.AsEnumerable()
.Select(
i => new
{
CityID = i.CityID,
Name = string.Format(
"{0} ({1:dd MMMM yyyy})",
i.CityName, i.MeetingDate),
Expired = i.MeetingDate < DateTime.UtcNow
}
).ToList();
var selectItems = new List<SelectListItem>(meetingsQuery.Count);
foreach (var record in meetingsQuery)
{
var item = new SelectListItem
{
Text = record.Name,
Value = record.Name
};
if (record.Expired)
{
item.Attributes.Add("class", "disabled"); //!!! Problem on this line
}
selectItems.Add(item);
}
ViewData["MeetingId"] = new SelectList(meetingsQuery, "CityID", "Name", selectedMeetings);
}
But after applying this method I got an error "'System.Web.Mvc.SelectListItem' does not contain a definition for 'Attributes' and no extension method 'Attributes' accepting a first argument of type...". So, I think I need to use another property or helper to assign class properties to custom racords (there is no problem filtering records on the "if (record.Expired)" line).
View:
#Html.Custom_DropdownList("MeetingId", ViewData["MeetingId"] as SelectList, new { id = "meetingId"})
Could you clarify me how to provide this? Thanks in advance.
Here is the modificated code for having ability for both Class and Disabled attributes:
Updated Code (for MyDropdownListFor):
Custom HTML Helper Class:
public static class MyHelpers
{
public class MySelectItem : SelectListItem
{
public string Class { get; set; }
public string Disabled { get; set; }
}
public static MvcHtmlString MyDropdownListFor<TModel, TProperty>(this HtmlHelper<TModel> htmlHelper, Expression<Func<TModel, TProperty>> expression, IEnumerable<MySelectItem> list, string optionLabel, object htmlAttributes)
{
return MyDropdownList(htmlHelper, ExpressionHelper.GetExpressionText(expression), list, optionLabel, HtmlHelper.AnonymousObjectToHtmlAttributes(htmlAttributes));
}
public static MvcHtmlString MyDropdownList(this HtmlHelper htmlHelper, string name, IEnumerable<MySelectItem> list, string optionLabel, IDictionary<string, object> htmlAttributes)
{
TagBuilder dropdown = new TagBuilder("select");
dropdown.Attributes.Add("name", name);
dropdown.Attributes.Add("id", name);
StringBuilder options = new StringBuilder();
// Make optionLabel the first item that gets rendered.
if (optionLabel != null)
options = options.Append("<option value='" + String.Empty + "'>" + optionLabel + "</option>");
foreach (var item in list)
{
if(item.Disabled == "disabled")
options = options.Append("<option value='" + item.Value + "' class='" + item.Class + "' disabled='" + item.Disabled + "'>" + item.Text + "</option>");
else
options = options.Append("<option value='" + item.Value + "' class='" + item.Class + "'>" + item.Text + "</option>");
}
dropdown.InnerHtml = options.ToString();
dropdown.MergeAttributes(new RouteValueDictionary(htmlAttributes));
return MvcHtmlString.Create(dropdown.ToString(TagRenderMode.Normal));
}
}
Controller:
private void PopulateMeetingsDropDownList(object selectedMeetings = null)
{
var meetingsQuery = repository.Meetings
.Join(repository.Cities, m => m.MeetingCityId, c => c.CityID,
(m, c) => new
{
CityID = c.CityID,
CityName = c.CityName,
MeetingDate = m.MeetingStartDate
}
)
.OrderBy(x => x.CityID)
.AsEnumerable()
.Select(
i => new
{
Value = i.CityID.ToString(),
DisplayValue = string.Format(
"{0} ({1:dd MMMM yyyy})",
i.CityName, i.MeetingDate),
Expired = i.MeetingDate < DateTime.UtcNow
}
).ToList();
var selectItems = new List<MyHelpers.MySelectItem>(meetingsQuery.Count);
foreach (var record in meetingsQuery)
{
var item = new MyHelpers.MySelectItem
{
Text = record.DisplayValue,
Value = record.Value
};
if (record.Expired)
{
item.Class = "disabled";
item.Disabled = "disabled";
}
selectItems.Add(item);
}
ViewBag.MeetingData = selectItems;
}
View:
<label>Meeting</label>
#Html.MyDropdownListFor(m => m.MeetingId, ViewBag.MeetingData as List<MyHelpers.MySelectItem>, "---- Select ----",
new { name = "meetingId", id = "meetingId"})
TL;DR; You are using SelectList, but you don't need to.
Create a new view model class, which you will pass to the view. It will contain all the properties you need, and may look like this:
Custom class to hold info about items:
public class CustomSelectItem
{
public string Text {get;set;}
public string Value {get;set;}
public string Class {get;set;}
public bool Selected {get;set;}
}
Since you are passing this data using ViewData, you don't have a limitation and you can put anything there. I advise that you use ViewBag instead of ViewData.
In your controller you can create a new List<CustomSelectItem> and pass it. When building the collection, if the item is expired just set the Class property to "disabled" or whatever you are using. Here is the code (I skipped the part where you get the meetings):
Controller:
var selectItems = new List<CustomSelectItem>(meetingsQuery.Count);
foreach (var record in meetingsQuery)
{
var item = new CustomSelectItem
{
Text = record.Name,
Value = record.Name
};
if (record.Expired)
{
item.Class = "disabled";
}
selectItems.Add(item);
}
ViewBag.MeetingData = selectItems;
Then modify the custom helper method you have created to accept a collection of CustomSelectItem instead of SelectListItem. You can write directly the HTML, because you have access to the Class property.
Custom helper method:
public static MvcHtmlString Custom_DropdownList(this HtmlHelper helper, string name, IList<CustomSelectItem> list, object htmlAttributes)
{
TagBuilder dropdown = new TagBuilder("select");
dropdown.Attributes.Add("name", name);
dropdown.Attributes.Add("id", name);
StringBuilder options = new StringBuilder();
foreach (var item in list)
{
options = options.Append("<option value='" + item.Value + "' class='" + item.Class + "'>" + item.Text + "</option>");
}
dropdown.InnerHtml = options.ToString();
dropdown.MergeAttributes(new RouteValueDictionary(htmlAttributes));
return MvcHtmlString.Create(dropdown.ToString(TagRenderMode.Normal));
}
Once this is done, you can invoke the helper from the view as follows:
View:
#Html.Custom_DropdownList("MeetingId", ViewBag.MeetingData as List<CustomListItem>, new { id = "meetingId"})
You can inherit the SelectList and add your own properties to the class, like this:
public class MySelectListItem : SelectListItem
{
public bool Highlighted { get; set; }
}
Once you have the custom class, you write your helper based on it. You can apply different css classes or change the style attribute.
public static class MyHtmlHelpers
{
public static MvcHtmlString DropDownListHighlighted(this HtmlHelper helper, string name, IEnumerable<MySelectListItem> itens)
{
StringBuilder dropDown = new St();
dropDown.AppendFormat("<select id='{0}' name='{0}'>", name);
foreach (var item in itens)
{
dropDown.AppendFormat("<option selected='{2}' value='{1}' class='{3}'>{0}</option>",
item.Text,
item.Value,
item.Selected ? "selected" : "",
item.Highlighted ? "highlighted" : "normal");
}
dropDown.Append("</select>");
return MvcHtmlString.Create(dropDown.ToString());
}
}
In your view (don't forget to add the namespace of your HTML helper class at the web.config or at the view):
#Html.DropDownListHighlighted("DropDown", ViewData["DropDownOptions"] as List<MySelectListItem>)
DropDownListHighlightedFor
You could do something like this to implementation a simple DropDownListHighlightedFor:
public static MvcHtmlString DropDownListHighlightedFor<TModel, TProperty>(this HtmlHelper<TModel> htmlHelper, Expression<Func<TModel, TProperty>> expression, IEnumerable<MySelectListItem> itens)
{
return DropDownListHighlighted(htmlHelper, ExpressionHelper.GetExpressionText(expression), itens);
}
In the view:
#Html.DropDownListHighlightedFor(x => x.Option, ViewData["DropDownOptions"] as List<MySelectListItem>)