Visiting an other URL at the server by code and extracting some data from there - asp.net-mvc-3

I am working in Asp.net MVC and have a peculiar requirement for which I have no idea.
What I want is that when a user request a particular URL from my site, I want to visit some preset URL in the database and extract some data and bind them to View before rendering.
For example, If you visit mysite.com/Search/Index, then in my action method Index, i want to visit the anothersite.com/someparticular/url, extract the value in <div> with id="searclbl", bind it to my view and render the page.
I need to read the HTML because the sites I am working with don't offer any Web services or RSS.
Any sort of help or guidance in this matter is appreciated.

I believe you might be able to pull this off using HtmlAgilityPack (which can be installed via a NuGet package inside your project).
For example:
Let’s assume your Index View of the SearchController is strongly typed to the following ViewModel:
public class SearchViewModel
{
public string DivElement { get; set; }
//other properties...
}
This is the Index ActionResult():
public ActionResult Index()
{
var model = new SearchViewModel();
model.DivElement = GetDivFromWebSite();
return View(model);
}
The GetDivFromWebSite() method is where I use HtmlAgilityPack to fetch information from another web site and is defined like so:
private string GetDivFromWebSite()
{
var baseUrl = new Uri("http://www.anotherdomaine.com");
HtmlAgilityPack.HtmlDocument document = new HtmlDocument();
using (WebClient client = new WebClient())
{
document.Load(client.OpenRead(baseUrl));
}
if (document == null) return "nothing found!";
var div = document.DocumentNode.SelectNodes("//div[#id='missing-category']").FirstOrDefault();
return div.InnerHtml;
}
This might do the trick!

Related

ASP.NET MVC Partial View Post-Back, Inconsistent Updating and Bizzare Behavior

I'm developing an ASP.Net MVC application, and am running into a bizzare issue when trying to update data in my database via a partial postback. I'm still new when it comes to HTTP, AJAX, etc., so I'm hoping it's an obvious error.
Basically, when I try to update a table linking content areas to assessments, the update sometimes works, sometimes doesn't. What's bizzare is that after I post, I query the database directly from the MVC application just to make sure that the expected change was in fact made (that's what all the ViewBag.DebugInfo's are doing in the code below). In every case, the query returns what I hope to see. But then when I query the table via SSMS, I see that the change only sometimes goes through.
How is it that a direct query to a table from my MVC application is showing that an update went through, while I can clearly see that it didn't via SSMS? Is there a silent rollback or something? This is maddening and any help would be much appreciated.
A few bits of info:
When I run the "saveTheData" function from the assessmentContent class below outside of MVC, it is always successful.
The update is always successful on the first post.
The update is successful only about half the time on subsequent posts.
When the update is not successful, the the direct query checks from the MVC application nevertheless do seem to show that the update made it all the way to the table.
I can barely make out a pattern in the failures. Namely, it seems that whenever I try to update to a higher contentId value, it is successful, if I try to update to a lower contentId, it is not. For instance, updating from a value of 1 (Math) to 2 (Reading) will always go through, but the reverse will not. This pattern does not manifest if it's the first post from the parent view, or if it's updated via Linqpad.
I put insert, update, and delete triggers on the database table that write to a logging table to see if perhaps the changes were being rolled back. But no entries on the log table on failure. But I also don't know if rollbacks would undo the triggers as well.
I queried dbo.fn_dblog() filtered for Operation = 'LOP_ABORT_XACT', but nothing (though I don't have trained eyes for that difficult view).
Here's my class that fetches and updates the data:
public class assessmentContent {
public int? assessmentId { get; set; }
public List<short> baseline { get; set; } = new List<short>();
public List<short> comparison { get; set; } = new List<short>();
public assessmentContent() { if (assessmentId != null) refreshTheData(); }
public assessmentContent(int assessmentId) {
this.assessmentId = assessmentId;
refreshTheData();
}
public void saveTheData() {
List<short> upserts = comparison.Except(baseline).ToList();
List<short> deletes = baseline.Except(comparison).ToList();
foreach (var upsert in upserts)
reval.ach.addAssessmentContent(assessmentId, upsert);
foreach (var delete in deletes)
reval.ach.deleteAssessmentContent(assessmentId, delete);
refreshTheData();
}
void refreshTheData() {
baseline = reval.ach.assessmentContent(assessmentId).ToList();
comparison = reval.ach.assessmentContent(assessmentId).ToList();
}
}
The logic works fine when I use it outside of my MVC application. So, for instance, if I use it via linqpad, there are no issues. I should mention that assessmentContent() could be named 'getAssessmentContent()'.
Here's my Controller for the Partial View, and some related Code:
public class ContentsModel {
public int? assessmentId { get; set; }
public List<short> comparison { get; set; }
}
public class ContentsController : Controller {
public static string nl = System.Environment.NewLine;
public ActionResult ContentsView(int assessmentId) {
ViewBag.DebugInfo = new List<string>();
var vm = new ContentsModel();
vm.assessmentId = assessmentId;
vm.comparison = reval.ach.assessmentContent(assessmentId).ToList();
return View("~/Views/ach/Contents/ContentsView.cshtml", vm);
}
public ActionResult update(ContentsModel vm) {
ViewBag.DebugInfo = new List<string>();
sqlFetch();
ViewBag.DebugInfo.Add($"VM Pased In {vm.assessmentId} c{vm.comparison.intsJoin()}");
sqlFetch();
var crud = new crud.ach.assessmentContent((int)vm.assessmentId);
ViewBag.DebugInfo.Add($"newly fetched CRUD {crud.assessmentId} b{crud.baseline.intsJoin()} c{crud.comparison.intsJoin()}");
sqlFetch();
crud.comparison = vm.comparison;
ViewBag.DebugInfo.Add($"CRUD after crud_comparison = vm_comparison {crud.assessmentId} b{crud.baseline.intsJoin()} c{crud.comparison.intsJoin()}");
sqlFetch();
crud.saveTheData();
ViewBag.DebugInfo.Add($"CRUD after save {crud.assessmentId} b{crud.baseline.intsJoin()} c{crud.comparison.intsJoin()}");
sqlFetch();
vm.comparison = crud.comparison;
ViewBag.DebugInfo.Add($"VM after vm_comparison = crud_comparison {vm.assessmentId} c{vm.comparison.intsJoin()}");
sqlFetch();
return PartialView("~/Views/ach/Contents/ContentsView.cshtml", vm);
}
void sqlFetch() {
ViewBag.DebugInfo.Add(
"SQL Fetch " +
Sql.ExecuteOneColumn<short>("select contentId from ach.assessmentContent where assessmentId = 12", connections.research).intsJoin()
);
}
}
public static partial class extensions {
public static string intsJoin(this IEnumerable<short> ints) {
var strings = new List<string>();
foreach (int i in ints)
strings.Add(i.ToString());
return string.Join(",", strings);
}
}
I'm aware that I might not have the 3-tier architecture or the Model-View-Controller structure best implemented here.
You'll notice that, in my desperation, I put in a direct check to the database table at every point of change in models.
The Partial View:
#model reval.Views.ach.Contents.ContentsModel
#using reval
#{Layout = "";}
<div id="contentDiv">
<form id="contentForm">
#Html.HiddenFor(m => m.assessmentId)
#Html.ListBoxFor(
m => m.comparison,
new reval.ach.content()
.GetEnumInfo()
.toMultiSelectList(
v => v.Value,
d => d.DisplayName ?? d.Description ?? d.Name,
s => Model.comparison.Contains((short)s.Value)
),
new { id = "contentListBox" }
)
</form>
<br/>
#foreach(string di in ViewBag.DebugInfo) {
#Html.Label(di)
<br/>
}
</div>
<script>
$("#contentListBox").change(function () {
$.ajax({
url: "/Contents/update",
type: "get",
data: $("#contentForm").serialize(),
success: function (result) {
$("#contentDiv").html(result);
},
error: function (request, status, error) {
var wnd = window.open("about:blank", "", "_blank");
wnd.document.write(request.responseText);
}
});
})
</script>
And Finally, the call from the main View:
<div id="testDiv">
#if (Model.assessment != null && Model.assessment.assessmentId != null) {
Html.RenderAction("ContentsView", "Contents", new { assessmentId = Model.assessment.assessmentId });
}
</div>
Are you sure that the transaction you are making to the database is committed or finalized? There could be other transactions going on at same time that could roll back the ones you are making.
I hate it when I solve an issue without exactly pinning down what I did to solve it. But after working on some of the code above, I got it working correctly and consistently.
I'm almost certain, however, that the issue had to do with a misunderstanding of how asp.net mvc works when passing data from server to client and back. Namely, I had an idea that my C# viewmodels and controllers on the server were still alive when their data was being sent to html/asp on the client. I had a hunch that the client data was not the same as the C# objects, but I did feel that ASP.Net MVC was updating the C# object for any changes on postback. Now it is clear to me that in fact the C# objects are fully discarded and completely instantiated (with the constructors called and all related consequences) and repopulated with data from client. And this is true even when no changes are made at the client.
I think that updates were in fact being made to the database. No rollback was occurring. But something was happening upon re-instantiation that was causing a second call to the database and resetting their values. This would explain why it was working perfectly outside of ASP.net MVC. It would explain why I solved the issue after this realization.
I would call this response accurate, but not precise. By that I mean that I have confidence that the guidance resolves the issue, even if it doesn't pin down the exact lines of offending code above. Due to the accuracy, I'm considering it fair game to mark it as an answer. Due to the imprecision, I'm open to marking someone else's response as the answer if they can be more precise. However, since the code above is no longer in use, it is all just for learning purposes.

Add custom data-* attributes to Kendo UI AutoComplete or ComboBox

Currently using the Kendo UI AutoCompleteFor() and ComboBoxFor() helper.
Noticing that they generate/render a bunch of <li>s.
How does one add additional custom data-* attributes to those <li>s?
Here's the current scenario:
The user starts typing stuff in the AutoCompleteFor
An ajax call is triggered to fetch some data related to what the
user has typed.
The obtained results are transformed into an
IEnumerable<SelectListItem>.
The result is then sent to Json. Json(result, JsonRequestBehavior.AllowGet)
My goal is to add one or more additional data-* attribute to each of these <li> generate lines so that I can fetch these data-* in the onChange() event.
How does one achieve this?
In addition, I'm aware that I could create my own .Template() and possibly achieve my task but I was curious if anyone knows of a different way to do this then having to create my own template.
Sincerely
Ok I've found a solution; I'll share it here in case anyone is interested.
Instead of transforming my obtained results into an IEnumerable<SelectListItem>, I simply transform this into an IEnumerable<CustomDTO>.
The CustomDTO class looks like this:
public class CustomDTO
{
public int Value { get; set; }
public string Text { get; set; }
public int Age { get; set; }
//Add whatever more properties you think you’ll need.
}
In my controller, I then do the following:
var result = _myService.GetData().ToList();
return Json(result, JsonRequestBehavior.AllowGet);
Where GetData() returns an IEnumerable<CustomDTO>.
Inside my View, I have an AutoCompleteFor() control to which I bind a client side
.Events(x => x.Select("onSelect") event handler.
The handler is defined like so:
function onSelect(e)
{
if (e.item == null) return;
var dataItem = this.dataItem(e.item.index());
var valueAttribute = dataItem.Value;
var textAttribute = dataItem.Text;
var ageAttribute = dataItem.Age; //This is how I get my additional value
//...code...
}
So that's it.

How do you read POST data in an ASP.Net MVC 3 Web API 2.1 controller?

This does not seem to be as easy as I thought. I found some solutions on the web, but they are not working for me. I have an ASP.Net MVC 3 project with the Microsoft ASP.Net Web API 2.1 nuget package installed. Now, I want to be able to read data posted to a web api controller. The data sent will vary, so I cannot used a strongly typed ViewModel.
Here are the solutions I tried:
public void Post([FromBody]string value)
{
...
}
public void Post([FromBody]List<string> values)
{
...
}
public void Post([FromBody]NameValueCollection values)
{
...
}
But my value or values variables are always empty. I know the controller is receiving data however because I can check it by accessing (System.Web.HttpContextWrapper)Request.Properties["MS_HttpContext"].Request.Form. It does not look like the proper way to retrieve the data though. There ought to be a cleaner way.
UPDATE:
Here is how I am posting the information:
I am posting the data from another controller in the same web application:
public ActionResult SendEmailUsingService()
{
dynamic email = new ExpandoObject();
email.ViewName = "EmailTest";
email.From = "fromaddress#yahoo.com";
email.To = "toaddress#gmail.com";
email.Fullname = "John Smith";
email.Url = "www.mysite.com";
IDictionary<string, object> data = email;
using (var wb = new WebClient())
{
string url = BaseUrlNoTrailingSlash + Url.RouteUrl("DefaultApi", new { httproute = "", controller = "Emailer" });
var response = wb.UploadValues(url, "POST", data.ToNameValueCollection());
}
return View();
}
And here is what I am getting in my Post web api controller if I declare an httpContext variable like this:
var httpContext = (System.Web.HttpContextWrapper)Request.Properties["MS_HttpContext"];
httpContext.Request.Form =
{ViewName=EmailTest&From=fromaddress%40yahoo.com&To=toaddress%40gmail.com&Fullname=John+Smith&Url=www.mysite.com}
httpContext.Request.Form is a System.Collections.Specialized.NameValueCollection {System.Web.HttpValueCollection}
I finally found the answer to my question here:
Web API Form Data Collection
The solution is to use FormDataCollection:
public void Post([FromBody]FormDataCollection formData)
{
...
}

Can I add a derived property to an EF entity and have it available to breeze?

I am using code first with an existing database, EF5, Web API and Breeze and I havent used any of these techs before. I am writing my own pocos.
I am trying to expose a read only property that requires several table joins to obtain the data. If we were using Web API only, we could just run some sql, populate the property and send some JSON back to the client.
Because we are using EF and breeze this obviously changes quite alot.
For example:
public class Employee
{
[Key]
public int EmployeeID { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
[NotMapped]
public string FooBar
{
get { return getFooBar(); }
}
}
private string getFooBar()
{
// Do stuff here
}
This will send FooBar back to the client in the JSON result but because it is not mapped, and consequently not in the Metadata, I dont seem to be able to use it within Breeze.
I have read articles that say I can do this when using designer based methods (ie edit the edmx file) but how can it be done using code first?
I am aware that I can extend a Breeze entity on the client side but Im not really sure how I would get this value which hasnt been mapped, after Breeze has created all of the entities.
What I really want is to extend my code first entity. I also vaguely understand that this might not be in line with EF ideals but I also struggle with the idea that I dont have the freedom to define what is and what isnt a property of my employee.
I dont need to track changes. I dont need to save. I dont seem to be able the use the EF context provider to join the (many) tables and get the data because the entities for each table dont share a primary key and dont inherit from the same class.
I think this SO post here suggests something similar but once again its for generated classes. Is there a way to do this? Thanks.
Edit
In reply to Wards suggestion I tried a few tests.
My client side constructor:
function Employee() {
this.DisplayName = ""; // unmapped property
};
My Controller:
function TestController($scope, $routeParams) {
var manager = new breeze.EntityManager('breeze/employees');
var metadataStore = manager.metadataStore;
metadataStore.registerEntityTypeCtor("Employee", Employee);
var query = new breeze.EntityQuery()
.from("Employees")
.orderBy("FirstName");
manager.executeQuery(query).then(function (data) {
// Check unmapped property name
var employeeType = metadataStore.getEntityType("Employee");
var unmapped = employeeType.unmappedProperties;
alert(unmapped[0].name) // Returns 'DisplayName'
alert(employeeType.dataProperties[3].name) // Returns 'DisplayName'
var prop = manager.metadataStore.getEntityType('Employee').getProperty('DisplayName');
alert(prop.name) // Returns 'DisplayName'
var first = data.results[0]
var fullName = first.DisplayName
alert(fullName) // Returns empty string
$scope.employees = data.results;
$scope.$apply();
}).fail(function (e) {
alert(e);
});
};
My Angular:
<div>
<ul>
<li data-ng-repeat="employee in employees">
{{employee.DisplayName}}
</li>
</ul>
</div>
So the property seems to be setup correctly as an unmapped property, but it only returns the empty string. If I change
this.DisplayName = ""; // unmapped property
to
this.DisplayName = "Foo"; // unmapped property
then DisplayName always contains "Foo". The values from the payload are not being applied to DisplayName.
Am I missing something?
It's pretty easy on the Breeze client as explained in the Extending Entities documentation topic: you define an unmapped property in a custom constructor and register that constructor.
var metadataStore = myEntityManager.metadataStore;
metadataStore .registerEntityTypeCtor("Employee", Employee);
function Employee ()
this.FooBar = ""; // unmapped property
};
Now the Breeze metadata includes a definition of the FooBar unmapped property. The server will send a value for FooBar to the client and Breeze will populate that client Employee entity (unmapped) property when it materializes Employee entities from a query.
How you obtain that FooBar property value on the server is up to you. I don't know enough about your app. What you've shown us is a perfectly valid Code First entity definition.
Maybe you're asking an Entity Framework question rather than a Breeze question.
One way to get this working has been discussed in this SO answer from CassidyK. Here is the code snippet.
proto.initializeFrom = function (rawEntity) {
// HACK:
// copy unmapped properties from newly created client entity to the rawEntity.
// This is so that we don't lose them when we update from the rawEntity to the target.
// Something that will occur immediately after this method completes.
var that = this;
this.entityType.unmappedProperties.forEach(function(prop) {
var propName = prop.name;
that[propName] = rawEntity[propName]; // CassidyK
//rawEntity[propName] = that[propName]; // Breeze
});
if (!this._backingStore) {
this._backingStore = { };
}
};
I dont know what the side effects of this are. Perhaps one of the Breeze devs can better explain.
It seems this is only a problem when Breeze is configured for Angular.
IE
breeze.config.initializeAdapterInstance("modelLibrary", "backingStore", true);

MVC 3 with Dynamic Data - applying data types to Dynamic Data

I have an MVC 3 C# / ADO.NET / Dynamic Data app set up and working(ish). To set it up I created an MVC 3 app, added the Dynamic Data components in, split out Presentation, Business and Data in to three projects, set the references to match the MVC pattern and set up the routes and scaffolding.
List, Edit and Insert all work with the standard DD page templates, however I've hit a wall getting the Presentation Layer to apply data type attributes to the data displayed in Gridview and Details views, particularly for URLs, which I want be typed as DataType.Url and so use the associated DD display attributes.
Have tried setting up a meta data class for the Link table and applying something like:
[DataType(DataType.Url)]
public object URL {get; set;}
(the Url field in table "Link" is "URL")
.. within a partial class, which is something I read about for pure DD sites.
Can anyone point me in the right direction, or tell me if is this even possible?
Many Thanks.
Yes this is possible. I would write a custom FieldTemplate for Urls. Using the UIHint metadata, you can assign a custom fieldtemplate to the column. Something like this (untested):
public partial class FooUrl : System.Web.DynamicData.FieldTemplateUserControl
{
string getUrl()
{
var metadata = MetadataAttributes.OfType<DataTypeAttribute>().FirstOrDefault();
if (metadata == null)
return FieldValueString;
switch (metadata.DataType)
{
case DataType.Url:
string url = FieldValueString;
if (url.StartsWith("http://", StringComparison.OrdinalIgnoreCase) ||
url.StartsWith("https://", StringComparison.OrdinalIgnoreCase))
return url;
return "http://" + FieldValueString;
case DataType.EmailAddress:
return "mailto:" + FieldValueString;
default:
throw new Exception("Unknown DataType");
}
}
protected override void OnDataBinding(EventArgs e)
{
HyperLinkUrl.NavigateUrl = getUrl();
}
public override Control DataControl
{
get
{
return HyperLinkUrl;
}
}
}
}
I hope this helps.

Resources