There has been a lot of discussions on View-engines for ASP.NET MVC and some criticisms against the inline "tag-soup" with for-loops and thing like it.
The alternative or complement has been to use HTML-helpers, which are just inline method-calls.
When I look inside ASP.NET MVC's HTML-helpers today they are using a class called TagBuilder.
My proposal is to use LINQ to XML to get strongly typed and correctly formatted (X)HTML:
XDocument output = new XDocument();
XElement root = new XElement("div",
new XAttribute("class", "root_item"));
XElement iconImage = new XElement("img",
new XAttribute("src", ResolveUrl("~/image.gif")),
new XAttribute("alt", "This is an image"));
XElement link = new XElement("a",
new XAttribute("class", "link"),
new XAttribute("href", "http://google.com"),
new XText("Link to Google"));
root.Add(link);
root.Add(iconImage);
output.Add(root);
I like it because it's like the strongly typed controls in WebForms, where you can new-up a Button and add it to another control's Control-collection.
Are there any apparent problems or limitations in this?
This is a great idea! The only problem I see with it is the use of C#. ;) VB.NET has much better support for producing XML via it's XML literals feature.
The code you list in your question could be written like this in VB.NET. (With the addition of the text "This is a link" as your example didn't contain any text within the a element.)
Dim root = <div class="root_item">
<img src=<%= ResolveUrl("~/image.gif") %> alt="This is an image"/>
<a class="link" href="http://google.com">This is a link</a>
</div>
There are still <%= ... %> tags, but they are checked for validity at compile time. If this code was made the return value of a function that returned type XElement, then that Xhtml snippet could be reused elsewhere in the site.
I have a project on CodePlex that uses VB.NET XML Literals as a custom ASP.NET MVC View Engine at http://vbmvc.codeplex.com. It is based on code by Dmitry Robsman, who is Product Unit Manager for ASP.NET at Microsoft. Views are VB.NET classes and Master Pages are base classes. You new-up Partial view classes instead of referencing them by a name string, so that is also an additional compile time check. Instead of the HtmlHelper class, which returns strings, there is an XhtmlHelper class which returns XElement and works similarly to what you have proposed.
I can think of two problems to the above mentioned method.
First,
XElement iconImage = new XElement("img",
new XAttribute("src", ResolveUrl("~/image.gif")),
new XAttribute("alt", "This is an image"));
Referring to what you write above, we can have something like:
<img src=<%=whatever%> alt=<%=whatever%> />
This might be personal judgement or what, but I certainly vote the later one more "human" readable. Right, using LINQ 2 XML might get rid of the weird <% } %> that wandering around in my aspx pages, but at the same time, you make those "good boys" looks clumsy.
Second might come with performance issue. I think parsing and executing LINQ 2 XML could be quite slow, although I don't have any data regarding this.
Personally I am still experimenting the MVC framework, it feels like getting back to old days like ASP or PHP 3.X, since almost all the interactive parts are explicitly handled, instead of the window/GUI-OOP oriented ASP Dot Net Framework. I think the main reason I will use MVC is that it can guarantee the best quality client-side HTML codes..
Related
There is this website that we purchase widgets from that provides details for each of their parts on its own webpage. Example: http://www.digikey.ca/product-search/en?lang=en&site=ca&KeyWords=AE9912-ND. I have to find all of their parts that are in our database, and add Manufacturer and Manufacturer Part Number values to their fields.
I was told that there is a way for Visual Basic to access a webpage and extract information. If someone could point me in the right direction on where to start, I'm sure I can figure this out.
Thanks.
How to scrape a website using HTMLAgilityPack (VB.Net)
I agree that htmlagilitypack is the easiest way to accomplish this. It is less error prone than just using Regex. The following will be how I deal with scraping.
After downloading htmlagilitypack*dll, create a new application, add htmlagilitypack via nuget, and reference to it. If you can use Chrome, it will allow you to inspect the page to get information about where your information is located. Right-click on a value you wish to capture and look for the table that it is found in (follow the HTML up a bit).
The following example will extract all the values from that page within the "pricing" table. We need to know the XPath value for the table (this value is used to instruct htmlagilitypack on what to look for) so that the document we create looks for our specific values. This can be achieved by finding whatever structure your values are in and right click copy XPath. From this we get...
//*[#id="pricing"]
Please note that sometimes the XPath you get from Chrome may be rather large. You can often simplify it by finding something unique about the table your values are in. In this example it is "id", but in other situations, it could easily be headings or class or whatever.
This XPath value looks for something with the id equal to pricing, that is our table. When we look further in, we see that our values are within tbody,tr and td tags. HtmlAgilitypack doesn't work well with the tbody so ignore it. Our new XPath is...
//*[#id='pricing']/tr/td
This XPath says look for the pricing id within the page, then look for text within its tr and td tags. Now we add the code...
Dim Web As New HtmlAgilityPack.HtmlWeb
Dim Doc As New HtmlAgilityPack.HtmlDocument
Doc = Web.Load("http://www.digikey.ca/product-search/en?lang=en&site=ca&KeyWords=AE9912-ND")
For Each table As HtmlAgilityPack.HtmlNode In Doc.DocumentNode.SelectNodes("//*[#id='pricing']/tr/td")
Next
To extract the values we simply reference our table value that was created in our loop and it's innertext member.
Dim Web As New HtmlAgilityPack.HtmlWeb
Dim Doc As New HtmlAgilityPack.HtmlDocument
Doc = Web.Load("http://www.digikey.ca/product-search/en?lang=en&site=ca&KeyWords=AE9912-ND")
For Each table As HtmlAgilityPack.HtmlNode In Doc.DocumentNode.SelectNodes("//*[#id='pricing']/tr/td")
MsgBox(table.InnerText)
Next
Now we have message boxes that pop up the values...you can switch the message box for an arraylist to fill or whatever way you wish to store the values. Now simply do the same for whatever other tables you wish to get.
Please note that the Doc variable that was created is reusable, so if you wanted to cycle through a different table in the same page, you do not have to reload the page. This is a good idea especially if you are making many requests, you don't want to slam the website, and if you are automating a large number of scrapes, it puts some time between requests.
Scraping is really that easy. That's is the basic idea. Have fun!
Html Agility Pack is going to be your friend!
What is exactly the Html Agility Pack (HAP)?
This is an agile HTML parser that builds a read/write DOM and supports
plain XPATH or XSLT (you actually don't HAVE to understand XPATH nor
XSLT to use it, don't worry...). It is a .NET code library that allows
you to parse "out of the web" HTML files. The parser is very tolerant
with "real world" malformed HTML. The object model is very similar to
what proposes System.Xml, but for HTML documents (or streams).
Looking at the source of the example page you provided, they are using HTML5 Microdata in their markup. I searched some more on CodePlex and found a microdata parser which may help too: MicroData Parser
My template modified from MvcMusicStore tutroial:
<script type="text/x-kendo-tmpl" id="template">
<div class="product">
<a href="#Url.Action("Details", "Store", new {id = ${ProductSubCategoryId} })">
<img src="#Url.Content("${AlbumArtUrl}")" alt="${Title} image" />
</a>
<span><h3>${Title}</h3></span>
<p>${kendo.toString(Price, "c")}</p>
</div>
</script>
but there is error in Url.Action method: Unexpected symbol '$'.
Update 1:
And i can't use
#Model.First(d => d.ProductCategoryId.Equals(Convert.ToInt32("${ProductSubCategoryId}"))).ProductCategory.Name
but same code in
#Url.Content("${AlbumArtUrl}")
work fine.
new {id = ${ProductSubCategoryId} }
This is C# code for an anonymous object, and C# doesn't know anything about JS templating. Hence the error telling you it doesn't know what that $ is doing there. Usually you'd pass something from your View model, which is only available serverside:
new {id = Model.ProductSubCategoryId }
Instead of using a URL helper, you might be better off with just a string href="Store/Details/${ProductSubCategoryId}". That may not be exactly what you need, but I don't know enough about the routing and your template to know if this is what you intended, or if ProductSubCategoryId is actually a property of your model.
In regards to your updated examples:
Url.Content( works because that function takes the parameter as the page is being rendered on the server, and just spits out with the string "${AlbumArtUrl}" in the HTML pretty much as it is, and the HTML will contain the string "${AlbumArtUrl}" so that when the JS template is parsed later on the client, it can interpret that variable. So in this case, the C# function .Content( doesn't need to understand that template variable, because to it, it is just a string that it embeds in the HTML. I would recommend using F12 in Chrome to view the GET response in the network tab so you can see the HTML source as it was returned from the action, so you have a better idea in your mind of what exactly what is happening at each step of the process. You'll be able to see that in your HTML returned, there is no C# code like Url.Content, but you will see the javascript template stuff like "${AlbumArtUrl}" because those values aren't rendered on the server side.
On the other hand Convert.ToInt32("${ProductSubCategoryId}") fails, because this function expects the string it's being passed to be an integer, such as Convert.ToInt32("2134"). As far as ToInt32 is concerned, it says, ok, I see "something" is a string, now I will try to interpret the string as a number which means I expect it to contain some digits, such as "456457" but you gave me a bunch of letters and symbols which means nothing to me, I can't convert that into an integer. Again, this is C# function that is running on the server as it generates the page, and "${ProductSubCategoryId}" means nothing to C# as it is a javascript template variable.
You are mixing server-side code with client-side code. This is invalid C# code:
#Url.Content("${AlbumArtUrl}")
You cannot use helper to generate URLs this way. I suggest you to go the normal way without using the Url helper.
If you are determined to use Url.Action, then here is a way. It is a hack but you can do this:
<a href="#Url.Action("Details", "Store")?id=${ProductSubCategoryId}">
As ScottGu says in his blog post «by default content emitted using a # block is automatically HTML encoded to better protect against XSS attack scenarios».
My question is: how can you output a non-HTML-encoded string?
For the sake of simplicity, pls stick to this simple case:
#{
var html = "<a href='#'>Click me</a>"
// I want to emit the previous string as pure HTML code...
}
This is my favorite approach:
#Html.Raw("<p>my paragraph text</p>")
Source was Phil Haack's Razor syntax reference: http://haacked.com/archive/2011/01/06/razor-syntax-quick-reference.aspx
You can create a new instance of MvcHtmlString which won't get HTML encoded.
#{
var html = MvcHtmlString.Create("<a href='#'>Click me</a>")
}
Hopefully there will be an easier way in the future of Razor.
If you're not using MVC, you can try this:
#{
var html = new HtmlString("<a href='#'>Click me</a>")
}
new HtmlString is definitely the answer.
We looked into some other razor syntax changes, but ultimately none of them ended up really being any shorter than new HtmlString.
We may, however, wrap that up into a helper. Possibly...
#Html.Literal("<p>something</p>")
or
#"<p>something</p>".AsHtml()
I'm using ASP.NET MVC and Razor under Mono.
I couldn't get HtmlHelper from System.Web.WebPages of System.Web.Mvc for some reasons.
But I managed to output unencoded string after declaring model's property as RazorEngine.Text.RawString. Now it outputs as expected.
Example
#{
var txt = new RawString("some text with \"quotes\"");
var txt2 = "some text with \"quotes\"";
}
<div>Here is unencoded text: #txt</div>
<div>Here is encoded text: #txt2</div>
Output:
<div>Here is unencoded text: some text with "quotes"</div>
<div>Here is encoded text: some text with "quotes"</div>
I ran into this problem as well when transitioning our project to the new Razor view engine. The approach I took was slightly different because we had to generate JSON data from C# and wanted to output it upon page load.
What I eventually did was to implement a RawView that was a parallel of View inside of the cshtml files. Essentially, to get a raw string,
#(new HtmlString(View.Foo))
// became
#RawView.Foo
This requires a few changes to the project layout, so I just wrote up a blog post about it here. In short, this required a duplicate implementation of MVC's DynamicViewDataDictionary and a new WebViewPage that contains the RawView. I also went ahead and implemented the index operator on the RawView to allow for
#RawView["Foo"]
In the off-chance that someone needs to loop over the data with a list of keys.
Reading anurse's comment, it probably would have been better off if I had named this as a Literal instead of RawView.
OK so I one page I find this line:
objDsCourse = SqlHelper.ExecuteDataset(ConfigurationManager.ConnectionStrings("connstr").ConnectionString, CommandType.StoredProcedure, "Course_NewReportGet_Get_Sav", objPAra)
And I copied it to another page to start modifying it to work there:
getData = SqlHelper.ExecuteDataset(ConfigurationManager.ConnectionStrings("connstr").ConnectionString, CommandType.StoredProcedure, "Course_NewReportGet_Get_Sav", objPAra)
However on the new page it underlines .ConnectionStrings saying that Non-invocable member 'System.Configuration.ConfigurationManager.ConnectionStrings' cannot be used like a method'... then why did it work in the other page??
EDIT: OK so I found in web.config what I think it is referencing because it says
<add name="ConnStr" connectionString="data source=..." />
Why would one page have access to this and the other not?
Is there any chance one page is using VB.NET, while the other is using C#?
I would agree with Daniel. In Visual Basic, both dictionary objects and methods are referenced by using parentheses. This can cause some confusion.
So in VB, ConfigurationManager.ConnectionStrings("connstr") would point to the ConnectionString object with the key "connstr" in the dictionary.
In C#, dictionary objects are referenced by square brackets [] so ConfigurationManager.ConnectionStrings("connstr") would literally mean "invoke the method ConnectionStrings of ConfigurationManager object using "connstr" as a parameter."
Long story short, check the <%# Page %> declaration at the top to make sure both pages are the same language. ... or, on the page with the error, change the line to use the ConfigurationManager.ConnectionStrings["connstr"] syntax.
Has anyone used jQuery to populate an autocomplete list on a textbox using ASP.NET webforms? If so, can anyone recommend a good method? From my reading so far, it seems like most people are using delimited lists rather than JSON to bring the items back. I'm open to any ideas that will get me up and running rather quickly.
I made a tutorial to do this with asp.net mvc but it should be almost identical for traditional webforms:
http://blogs.msdn.com/joecar/archive/2009/01/08/autocomplete-with-asp-net-mvc-and-jquery.aspx
There are many, many examples on the web. I've used this one before, and if I recall you only need to create an aspx that will return matching terms as a <BR/> separated list:
http://www.dyve.net/jquery/?autocomplete
The documentation shows php in the example, but there's no difference in the way the plugin itself works and I didn't have to do anything special as a result.
From the documentation:
> $("#input_box").autocomplete("my_autocomplete_backend.php");
In the above example, Autocomplete
expects an input element with the id
"input_box" to exist. When a user
starts typing in the input box, the
autocompleter will request
my_autocomplete_backend.php with a GET
parameter named q that contains the
current value of the input box. Let's
assume that the user has typed
"foo"(without quotes). Autocomplete
will then request
my_autocomplete_backend.php?q=foo.
The backend should output possible
values for the autocompleter, each on
a single line. Output cannot contain
the pipe symbol "|", since that is
considered a separator (more on that
later).
An appropiate simple output would be:
foo
fool
foot
footloose
foo fighters
food fight
I wrote an Asp.Net WebControl and some Asp.Net MVC extension methods wrapping the JQuery UI autocomplete widget.
I wrote documentation as well about how to implement a working resource providing a JSon result.
You can find it at:
http://autocompletedotnet.codeplex.com/
Hope it can help