I am using the entity framework with MVC3 and am trying to do a search on a description field but the problem is that description field has HTML in it eg "< div class="section" />". Can i do a funky search that searches only the stuff outside of the HTML tags?
return context.Categories
.Where(i =>
i.Name.Contains(searchText)
&& i.Description.Contains(searchText)
)
Thanks in advance!
Give HtmlAgilityPack a go. It has methods for extracting the text out of an HTML Document.
You basically just need to do the following:
var doc = new HtmlDocument();
doc.LoadHtml(htmlStr);
var node = doc.DocumentNode;
var textContent = node.InnerText;
Or the much less awesome method:
public static string StripHTML(string htmlString)
{
string pattern = #"<(.|\n)*?>";
return Regex.Replace(htmlString, pattern, string.Empty);
}
All together
return StripHTML(context.Categories.Where(i => i.Name.Contains(searchText)&& i.Description.Contains(searchText)))
Related
I'm using Sitecore 7.2 with MVC and a component approach to page building. This means that pages are largely empty and the content comes from the various renderings placed on the page. However, I would like the search results to return the main pages, not the individual content pieces.
Here is the basic code I have so far:
public IEnumerable<Item> GetItemsByKeywords(string[] keywords)
{
var index = ContentSearchManager.GetIndex("sitecore_master_index");
var allowedTemplates = new List<ID>();
IEnumerable<Item> items;
// Only Page templates should be returned
allowedTemplates.Add(new Sitecore.Data.ID("{842FAE42-802A-41F5-96DA-82FD038A9EB0}"));
using (var context = index.CreateSearchContext(SearchSecurityOptions.EnableSecurityCheck))
{
var keywordsPredicate = PredicateBuilder.True<SearchResultItem>();
var templatePredicate = PredicateBuilder.True<SearchResultItem>();
SearchResults<SearchResultItem> results;
// Only return results from allowed templates
templatePredicate = allowedTemplates.Aggregate(templatePredicate, (current, t) => current.Or(p => p.TemplateId == t));
// Add keywords to predicate
foreach (string keyword in keywords)
{
keywordsPredicate = keywordsPredicate.And(p => p.Content.Contains(keyword));
}
results = context.GetQueryable<SearchResultItem>().Where(keywordsPredicate).Filter(templatePredicate).GetResults();
items = results.Hits.Select(hit => hit.Document.GetItem());
}
return items;
}
You could create a computed field in the index which looks at the renderings on the page and resolves each rendering's data source item. Once you have each of those items you can index their fields and concatenate all of this data together.
One option is to do this with the native "content" computed field which is natively what full text search uses.
An alternative solution is to make an HttpRequest back to your published site and essentially scrape the HTML. This ensures that all renderings are included in the index.
You probably will not want to index common parts, like the Menu and Footer, so make use of HTMLAgilityPack or FizzlerEx to only return the contents of a particular parent container. You could get more clever to remove inner containers is you needed to. Just remember to strip out the html tags as well :)
using HtmlAgilityPack;
using Fizzler.Systems.HtmlAgilityPack;
//get the page
var web = new HtmlWeb();
var document = web.Load("http://localsite/url-to-page");
var page = document.DocumentNode;
var content = page.QuerySelector("div.main-content");
I have converted my MVC3 application to MVC5, I had to change all views to razor. Having a challenge with a select list:
In ASPX view that works I am using the following:
<select id="Profession" name="Profession" style="width: 235px; background-color: #FFFFCC;">
<% List<string> allProfessions = ViewBag.AllProfessions;
string selectedProfession;
if (Model != null && !String.IsNullOrEmpty(Model.Profession))
selectedProfession = Model.Profession;
else
selectedProfession = allProfessions[0];
foreach (var aProfession in allProfessions)
{
string selectedTextMark = aProfession == selectedProfession ? " selected=\"selected\"" : String.Empty;
Response.Write(string.Format("<option value=\"{0}\" {1}>{2}</option>", aProfession, selectedTextMark, aProfession));
}%>
</select>
In Razor I am using:
<select id="Profession" name="Profession" style="width: 235px; background-color: #FFFFCC;">
#{List<string> allProfessions = ViewBag.AllProfessions;
string selectedProfession;}
#{if (Model != null && !String.IsNullOrEmpty(Model.Profession))
{selectedProfession = Model.Profession;}
else {selectedProfession = allProfessions[0];}
}
#foreach (var aProfession in allProfessions)
{
string selectedTextMark = aProfession == selectedProfession ?
"selected=\"selected\"" : String.Empty;
Response.Write(string.Format("<option value=\"{0}\" {1}>{2}</option>",
aProfession, selectedTextMark, aProfession));
}
</select>
The list shows up at the top of the page, I can't figure out where is the problem. Would appreciate your assistance.
Don't create your dropdown manually like that. Just use:
#Html.DropDownListFor(m => m.Profession, ViewBag.AllProfessions, new { style = "..." })
UPDATE
I tried your solution but got this error: Extension method cannot by dynamically dispatched
And, that's why I despise ViewBag. I apologize, as my answer was a little generic. Html.DropDownList requires the list of options parameter to be an IEnumerable<SelectListItem>. Since ViewBag is a dynamic, the types of its members cannot be ascertained, so you must cast explicitly:
(IEnumerable<SelectListItem>)ViewBag.AllProfessions
However, your AllProfessions is a simple array, so that cast won't work when the value gets inserted at run-time, but that can be easily fixed by casting it to a List<string> and then converting the items with a Select:
((List<string>)ViewBag.AllProfessions).Select(m => new SelectListItem { Value = m, Text = m })
There again, you see why dynamics are not that great, as that syntax is rather awful. The way you should be handling this type of stuff is to use your model or, preferably, view model to do what it should do: hold domain logic. Add a property to hold your list of profession choices:
public IEnumerable<SelectListItem> ProfessionChoices { get; set; }
And then, in your controller action, populate this list before rendering the view:
var model = new YourViewModel();
...
model.ProfessionChoices = repository.GetAllProfessions().Select(m => new SelectListItem { Value = m.Name, Text = m.Name });
return View(model);
repository.GetAllProfessions() is shorthand for whatever you're using as the source of your list of professions, and the Name property is shorthand for how you get at the text value of the profession: you'll need to change that appropriately to match your scenario.
Then in your view, you just need to do:
#Html.DropDownListFor(m => m.Profession, Model.ProfessionChoices)
Given that you don't have this infrastructure already set up, it may seem like a lot to do just for a drop down list, and that's a reasonable thing to think. However, working in this way will keep your view lean, make maintenance tons easier, and best of all, keep everything strongly-typed so that if there's an issue, you find out at compile-time instead of run-time.
I believe it's happening because of the Response.Write. Try this:
#Html.Raw(string.Format("<option value=\"{0}\" {1}>{2}</option>", aProfession,
selectedTextMark, aProfession))
I'm having trouble converting text to a hyperlink in a controller, then sending it to the view.
So far, I have:
Controller:
foreach (dynamic tweet in Timeline())
{
string text = tweet["text"].ToString();
const string pattern = #"http(s)?://([\w+?\.\w+])+([a-zA-Z0-9\~\!\#\#\$\%\^\&\*\(\)_\-\=\+\\\/\?\.\:\;\'\,]*)?";
Regex regexCheck = new Regex(pattern);
MatchCollection matches = regexCheck.Matches(text);
for (int i = 0; i < matches.Count; i++)
{
text = string.Format(#"<a href={0}>{1}</a>", matches[i].Value, matches[i].Value);
}
timeline.Add(text);
}
View:
#Html.DisplayFor(model => model.Timeline)
But It keeps displaying the literal text!
Display: <a href=http://t.co/fm0ruxjVXe>http://t.co/fm0ruxjVXe</a>
Could anyone please show me how this is done? I am quite new to MVC.
since you are sending out html already, try
#Html.Raw(Model.Timeline)
Another option is to send out just the url and the text and build the a tag in the view
#Model.Timeline.Text
That will involve changing your timeline property to being an object with two properties, text and url .
The #Html.DisplayFor helpers in Razor automatically encode the output
If you want to just output the content of model.Timeline, try just using Response.Write
I have a dynamic list of data with a dynamic number of columns being created by a PIVOT function. Everything more or less works, but I wanted to apply some custom formatting to some of the columns. I figured out how to get a list of the columns by just taking the first row and casting it like so:
var columns = Model.Comparisons.Select(x => x).FirstOrDefault() as IDictionary<string, object>;
Next I decided to create my List by looping over the "columns", which works as long as I reference the dynamic fields in the "format:" clause by their dynamic field name directly for example:
foreach (var c in columns)
{
switch (c.Key)
{
case "Cost":
cols.Add(grid.Column(
columnName: c.Key,
header: c.Key,
format: (item) => Convert.ToDecimal(item.Cost).ToString("C")));
break;
default:
cols.Add(grid.Column(columnName: c.Key, header: c.Key, format: item => item[c.Key]));
break;
}
}
The "default" does not dynamically get the value of each record. I believe it has to do with the "item[c.Key]" vs item.Cost. The problem is I don't want to have to write different case for each field, primarily because I don't know them ahead of time as the data can change. There are about 6 fields that will always be present. I do know however the datatype, which is why I wanted to put a custom format on them.
EDIT
I managed to solve this by writing an extension method.
public static class DynamicDataHelper
{
public static WebGridColumn GetColumn(this HtmlHelper helper, string vendor)
{
return new WebGridColumn()
{
ColumnName = vendor,
Header = vendor,
Format = (item) => helper.ActionLink(
(string)Convert.ToDecimal(item[vendor]).ToString("C"),
"VendorSearch",
"Compare",
new { Vendor = vendor, mpn = item.MPN },
new { target = "_blank" })
};
}
}
I edited my post with the Html Helper that I wrote that will in effect build the custom WebGridColumn objects I was having problems with. The "vendor" is passed in from the View and is then resolved at runtime. It works great.
I would like to create a helper to return a text one character below the other. Something like that:
S
A
M
P
L
E
The purpose of this helper is to have a table with a heading of only 1 character wide. As you can see on the picture below this is ugly:
Example below looks nice:
I would like something like:
#Html.DisplayVerticalFor(x => x.MyText)
Any idea?
Thanks.
You can create an HTML helper DisplayVertical. (I am not adding the steps of how to create html helpers). The DisplayVertical will first split your text in character array and wrap each character inside a div or any other block level element, which can be inserted desired place.The implementation of DisplayVerticalFor can be something like this :
public static MvcHtmlString DisplayVertical (this HtmlHelper helper, string text)
{
string OutputString = "";
string assembleString = "<div>{0}</div>";
char[] textarr = text.ToCharArray();
foreach( char a in textarr )
{
OutputString += String.Format(assembleString, a);
}
return new MvcHtmlString(OutputString);
}
and in razor it will placed like this :
<div class="style-to-adjust-width-n-height"> #Html.DisplayVertical ("Sample") </div>
If you want to pass on a lambda expression to this html helper like this #Html.DisplayVerticalFor(x => x.MyText) then you need to add lambda expression parsing code to find out the text.
Lastly, this is a very rough code however you can add "TagBuilder" etc to make it more neat and clean.