We are trying to embed an SSRS 2016 mobile report into our MVC Application.
We attempted to implement a "Reverse Proxy using HTTP Handler while SSRS authentication is set to basic".
The reason behind the use of Basic Authentication is that not all users that are accessing this page have NTLM access.
In the rsreportserver.config we set the Authentication to be as follows:
<Authentication>
<AuthenticationTypes>
<RSWindowsBasic>
<LogonMethod>3</LogonMethod>
<Realm></Realm>
<DefaultDomain>workgroup</DefaultDomain>
</RSWindowsBasic>
</AuthenticationTypes>
<RSWindowsExtendedProtectionLevel>Off</RSWindowsExtendedProtectionLevel>
<RSWindowsExtendedProtectionScenario>Proxy</RSWindowsExtendedProtectionScenario>
<EnableAuthPersistence>true</EnableAuthPersistence>
</Authentication>
So in general the link that is being redirected to within our page is partitioned as : //AppServer/KEYWORD/ReportPath
In the codebehind we are setting the handlers not only for KEYWORD but also for the elements within the page like assests and api. Such that we are getting the entire content of the mobile report
(//Server/ReportS/mobilereport/ReportPath) and writing it to our OutputStream.
Within the handler the authentication header for the request is being set.
The issue is that OAuth.js keeps failing with net: ERR_CONNECTION_RESET.
And we are never redirected to that specific report but to the homepage even if rs:Embed=true is included in the URL.
Why is it constantly failing?
And how can I directly load the report?
Code snippet from Handler:
public void Process(HttpContext context) {
HttpResponse response = context.Response;
string uri = "value";
HttpWebRequest webRequest = (HttpWebRequest)WebRequest.Create(new Uri(uri));
webRequest.Method = context.Request.HttpMethod;
webRequest.ImpersonationLevel = System.Security.Principal.TokenImpersonationLevel.Impersonation;
string svcCredentials = Convert.ToBase64String(ASCIIEncoding.ASCII.GetBytes(username + ":" + password));
webRequest.Headers.Add("Authorization", "Basic " + svcCredentials);
HttpWebResponse serverResponse = null;
try {
serverResponse = (HttpWebResponse)webRequest.GetResponse();
}
catch (WebException webExc)
{
response.StatusCode = 500;
response.StatusDescription = webExc.Status.ToString();
response.End();
return;
}
response.ContentType = serverResponse.ContentType;
const int blockSize = 2048;
byte[] contentBlock = new byte[blockSize];
long bytesToRead = serverResponse.ContentLength;
Stream dataStream = serverResponse.GetResponseStream();
while (bytesToRead > 0) {
int blockToRead = (int)Math.Min(blockSize, bytesToRead);
int bytesRead = dataStream.Read(contentBlock, 0, blockToRead);
bytesToRead -= bytesRead;
context.Response.OutputStream.Write(contentBlock, 0, bytesRead);
}
serverResponse.Close();
context.Response.OutputStream.Close();
context.Response.End();
}
Note: We are using this handler cause we are aware that the report-viewer does not cater to mobile reports
Related
We just started using SignalR in an MVC application and now we're getting a bunch of alerts due to high average response time. I suspect this to be misleading as the application isn't experiencing performance degradation. It appears that SignalR uses this URL to make a connection. This url not a controller/action of the project and just the built in SignalR code in the js file. jquery.signalR-2.2.1.js is the file. I suspect that it is just leaving the websocket connection open while they are on this page and it's skewing our numbers. Is this accurate? If so, is there a way to filter it out of the application insights?
Here is the counter. Is this the expected behavior?
Here is the signalR jquery code where it builds it's url:
// BUG #2953: The url needs to be same otherwise it will cause a memory leak
getUrl: function (connection, transport, reconnecting, poll, ajaxPost) {
/// <summary>Gets the url for making a GET based connect request</summary>
var baseUrl = transport === "webSockets" ? "" : connection.baseUrl,
url = baseUrl + connection.appRelativeUrl,
qs = "transport=" + transport;
if (!ajaxPost && connection.groupsToken) {
qs += "&groupsToken=" + window.encodeURIComponent(connection.groupsToken);
}
if (!reconnecting) {
url += "/connect";
} else {
if (poll) {
// longPolling transport specific
url += "/poll";
} else {
url += "/reconnect";
}
if (!ajaxPost && connection.messageId) {
qs += "&messageId=" + window.encodeURIComponent(connection.messageId);
}
}
url += "?" + qs;
url = transportLogic.prepareQueryString(connection, url);
if (!ajaxPost) {
url += "&tid=" + Math.floor(Math.random() * 11);
}
return url;
},
I fixed this by following the instructions on https://learn.microsoft.com/en-us/azure/application-insights/app-insights-api-filtering-sampling:
Update your ApplicationInsights Nuget package to 2.0.0 or later.
Create a class implementing ITelemetryProcessor:
public class UnwantedTelemetryFilter : ITelemetryProcessor
{
private ITelemetryProcessor Next { get; set; }
public UnwantedTelemetryFilter(ITelemetryProcessor next)
{
this.Next = next;
}
public void Process(ITelemetry item)
{
var request = item as RequestTelemetry;
if (request != null && request.Name != null)
if (request.Name.Contains("signalr"))
return;
// Send everything else:
this.Next.Process(item);
}
}
Add the processor to your Application_Start() in Global.asax.cs:
var builder = TelemetryConfiguration.Active.TelemetryProcessorChainBuilder;
builder.Use((next) => new UnwantedTelemetryFilter(next));
builder.Build();
if the calls are coming from the C# part of the app, the easiest way is to write a custom telemetry processor:
https://learn.microsoft.com/en-us/azure/application-insights/app-insights-api-filtering-sampling
public void Process(ITelemetry item)
{
var request = item as RequestTelemetry;
if (request != null && request.[some field here].Equals("[some signalr specific check here]", StringComparison.OrdinalIgnoreCase))
{
// To filter out an item, just terminate the chain:
return;
}
// Send everything else:
this.Next.Process(item);
}
and use that to explicitly filter out the signalr calls from being sent
or if the calls are coming from JS, then the telemetry initializer there does a similar thing to filter out telemetry if you return false in the initializer.
I'm attempting to replicate the OAuth steps normally done via the "Connect to QuickBooks" button using HttpWebRequest/HttpWebResponse.
It's easy at first grabbing the request token and generating the authorization link:
private const string oauthBaseUrl = "https://oauth.intuit.com/oauth/v1";
private const string urlRequestToken = "/get_request_token";
private const string urlAccessToken = "/get_access_token";
private const string verifyUrl = "https://appcenter.intuit.com";
private const string authorizeUrl = "https://appcenter.intuit.com/Connect/Begin";
...
var consumerContext = new OAuthConsumerContext
{
ConsumerKey = System.Utilities.Cryptography.Encryption.ConvertToUnsecureString(ckSS),
ConsumerSecret = System.Utilities.Cryptography.Encryption.ConvertToUnsecureString(csSS),
SignatureMethod = SignatureMethod.HmacSha1
};
IOAuthSession session = new OAuthSession(consumerContext, oauthBaseUrl + urlRequestToken, authorizeUrl, oauthBaseUrl + urlAccessToken);
IToken requestToken = session.GetRequestToken();
string authorizationLink = session.GetUserAuthorizationUrlForToken(requestToken, callbackUrl);
Then I walk through grabbing the request verification code that is generated in the set-cookie string when requesting the site at the authorization link:
var requestAuth = (HttpWebRequest) WebRequest.Create(authorizationLink);
requestAuth.Method = "GET";
requestAuth.ContentType = "application/x-www-form-urlencoded";
requestAuth.Accept = "text/html, application/xhtml+xml, */*";
requestAuth.Headers.Add("Accept-Encoding", "gzip, deflate");
requestAuth.Headers.Add("Accept-Language", "en-us");
requestAuth.Host = "appcenter.intuit.com";
requestAuth.KeepAlive = true;
var responseAuth = (HttpWebResponse) requestAuth.GetResponse();
Stream answerAuth = responseAuth.GetResponseStream();
var _answerAuth = new StreamReader(answerAuth);
string htmlAuth = _answerAuth.ReadToEnd();
// Need to grab the request verification code embedded in the set-cookie string
string cookies = responseAuth.Headers.Get("Set-Cookie");
int idx = cookies.IndexOf("__RequestVerificationToken", StringComparison.Ordinal);
if (idx > 0)
{
int startIndex = cookies.IndexOf("=", idx, StringComparison.InvariantCultureIgnoreCase);
int endIndex = cookies.IndexOf(";", startIndex + 1, StringComparison.InvariantCultureIgnoreCase);
requestVerificationCode = cookies.Substring(startIndex + 1, endIndex - (startIndex + 1));
postDataString += requestVerificationCode;
}
As I understand it, the request verification code is needed in order to get the OAuth verification code that is returned in the postdata appended to the callback URL, which is in turn needed to get the access token.
This is where the difficulty begins. Using Fiddler2, I find that the login URL for generating the OAuth verification code is https://appcenter.intuit.com/Account/LogOnJson. But no matter how much I try to replicate the HTTP POST using HttpWebRequest, all I get in return is a 500 error. I'm wondering if anyone has a working example of this step? Is this even possible? I hope so, because the alternative of pulling up IE and walking through the same steps like a macro is too ugly.
Any help on this? Thanks!
You can download the dotnet sample app for understanding how the OAUTH flow works:
https://github.com/IntuitDeveloperRelations/IPP_Sample_Code
Set your app keys in web.config.
I'm using the CSOM to upload files to a Sharepoint 365 site.
I've logged in succesfully with Claims based authentication using methods found here "http://www.wictorwilen.se/Post/How-to-do-active-authentication-to-Office-365-and-SharePoint-Online.aspx"
But using SaveBinaryDirect on the ClientContext fails with a 405 due to cookies being attached to request too late.
Another method of using CSOM to upload files is similar to below. But with SP 365, this limits the file size to about 3 meg.
var newFileFromComputer = new FileCreationInformation
{
Content = fileContents,
Url = Path.GetFileName(sourceUrl)
};
Microsoft.SharePoint.Client.File uploadedFile = customerFolder.Files.Add(newFileFromComputer);
context.Load(uploadedFile);
context.ExecuteQuery();
Is there ANY way to do this using CSOM, SP 365 and file sizes up to say 100 meg?
Indeed while trying to upload a file in SharePoint Online which size exceeds 250MB file limit the following exception will occur:
Response received was -1,
Microsoft.SharePoint.Client.InvalidClientQueryExceptionThe request
message is too big. The server does not allow messages larger than
262144000 bytes.
To circumvent this error chunked file upload methods have been introduced which support uploading files larger than 250 MB. In the provided link there is an sample which demonstrates how to utilize it via SharePoint CSOM API.
Supported versions:
SharePoint Online
SharePoint On-Premise 2016 or above
The following example demonstrates how to utilize chunked file upload methods in SharePoint REST API:
class FileUploader
{
public static void ChunkedFileUpload(string webUrl, ICredentials credentials, string sourcePath, string targetFolderUrl, int chunkSizeBytes, Action<long, long> chunkUploaded)
{
using (var client = new WebClient())
{
client.BaseAddress = webUrl;
client.Credentials = credentials;
client.Headers.Add("X-FORMS_BASED_AUTH_ACCEPTED", "f");
var formDigest = RequestFormDigest(webUrl, credentials);
client.Headers.Add("X-RequestDigest", formDigest);
//create an empty file first
var fileName = System.IO.Path.GetFileName(sourcePath);
var createFileRequestUrl = string.Format("/_api/web/getfolderbyserverrelativeurl('{0}')/files/add(url='{1}',overwrite=true)", targetFolderUrl, fileName);
client.UploadString(createFileRequestUrl, "POST");
var targetUrl = System.IO.Path.Combine(targetFolderUrl, fileName);
var firstChunk = true;
var uploadId = Guid.NewGuid();
var offset = 0L;
using (var inputStream = System.IO.File.OpenRead(sourcePath))
{
var buffer = new byte[chunkSizeBytes];
int bytesRead;
while ((bytesRead = inputStream.Read(buffer, 0, buffer.Length)) > 0)
{
if (firstChunk)
{
var endpointUrl = string.Format("/_api/web/getfilebyserverrelativeurl('{0}')/startupload(uploadId=guid'{1}')", targetUrl, uploadId);
client.UploadData(endpointUrl, buffer);
firstChunk = false;
}
else if (inputStream.Position == inputStream.Length)
{
var endpointUrl = string.Format("/_api/web/getfilebyserverrelativeurl('{0}')/finishupload(uploadId=guid'{1}',fileOffset={2})", targetUrl, uploadId, offset);
var finalBuffer = new byte[bytesRead];
Array.Copy(buffer, finalBuffer, finalBuffer.Length);
client.UploadData(endpointUrl, finalBuffer);
}
else
{
var endpointUrl = string.Format("/_api/web/getfilebyserverrelativeurl('{0}')/continueupload(uploadId=guid'{1}',fileOffset={2})", targetUrl, uploadId, offset);
client.UploadData(endpointUrl, buffer);
}
offset += bytesRead;
chunkUploaded(offset, inputStream.Length);
}
}
}
}
public static string RequestFormDigest(string webUrl, ICredentials credentials)
{
using (var client = new WebClient())
{
client.BaseAddress = webUrl;
client.Credentials = credentials;
client.Headers.Add("X-FORMS_BASED_AUTH_ACCEPTED", "f");
client.Headers.Add("Accept", "application/json; odata=verbose");
var endpointUrl = "/_api/contextinfo";
var content = client.UploadString(endpointUrl, "POST");
var data = JObject.Parse(content);
return data["d"]["GetContextWebInformation"]["FormDigestValue"].ToString();
}
}
}
Source code: FileUploader.cs
Usage
var userCredentials = GetCredentials(userName, password);
var sourcePath = #"C:\temp\jellyfish-25-mbps-hd-hevc.mkv"; //local file path
var targetFolderUrl = "/Shared Documents"; //library reltive url
FileUploader.ChunkedFileUpload(webUrl,
userCredentials,
sourcePath,
targetFolderUrl,
1024 * 1024 * 5, //5MB
(offset, size) =>
{
Console.WriteLine("{0:P} completed", (offset / (float)size));
});
References
Always use File Chunking to Upload Files > 250 MB to SharePoint Online
Well, I haven't found a way to do it with the CSOM and that is truly infuriating.
A work around was posted by SEvans at the comments on http://www.wictorwilen.se/Post/How-to-do-active-authentication-to-Office-365-and-SharePoint-Online.aspx .
Basically just do an http put and attach the cookie collection from the claims based authentication. SEvans posted workaround is below
Great piece of code Wichtor. As others have noted, SaveBinaryDirect does not work correctly, as the FedAuth cookies never get attached to the HTTP PUT request that the method generates.
Here's my workaround:
// "url" is the full destination path (including filename, i.e. https://mysite.sharepoint.com/Documents/Test.txt)
// "cookie" is the CookieContainer generated from Wichtor's code
// "data" is the byte array containing the files contents (used a FileStream to load)
System.Net.ServicePointManager.Expect100Continue = false;
HttpWebRequest request = HttpWebRequest.Create(url) as HttpWebRequest;
request.Method = "PUT";
request.Accept = "*/*";
request.ContentType = "multipart/form-data; charset=utf-8";
request.CookieContainer = cookie; request.AllowAutoRedirect = false;
request.UserAgent = "Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 6.1; WOW64; Trident/5.0)";
request.Headers.Add("Accept-Language", "en-us");
request.Headers.Add("Translate", "F"); request.Headers.Add("Cache-Control", "no-cache"); request.ContentLength = data.Length;
using (Stream req = request.GetRequestStream())
{ req.Write(data, 0, data.Length); }
HttpWebResponse response = (HttpWebResponse)request.GetResponse();
Stream res = response.GetResponseStream();
StreamReader rdr = new StreamReader(res);
string rawResponse = rdr.ReadToEnd();
response.Close();
rdr.Close();
I am trying to call a POST operation from Silverlight. My post is handled by an MVC3 controller, which returns a Redirect once it's finished processing the POST data. I use the HttpWebRequest object and everything works fine.
I want to be able to monitor and report the progress of the POST as it is uploading a large file. I have started by implementing a variation of the code here.
My problem occurs when I set request.AllowWriteStreamBuffering to false. My async callback method is never fired.
If I change the POST handler to not return the redirect, everything works as expected again.
Does anyone have any idea how to get around this?
Here is my code
var request = (HttpWebRequest) WebRequestCreator.ClientHttp.Create(new
Uri("http://localhost:3399/items/upload"));
request.Method = "POST";
string boundary = "---------------" + DateTime.Now.Ticks;
request.ContentType = "multipart/form-data; boundary=" + boundary;
request.AllowWriteStreamBuffering = false;
request.ContentLength = CalculatePostLength();
request.BeginGetRequestStream(asyncResult =>
{
Stream stream = request.EndGetRequestStream(asyncResult);
var writer = new StreamWriter(stream);
//Write filename
writer.WriteLine(boundarySeparator);
writer.WriteLine(nameheader);
writer.WriteLine();
writer.WriteLine(title);
//Write file
writer.WriteLine(boundarySeparator);
writer.WriteLine(fileHeader);
writer.WriteLine(contentType);
writer.WriteLine(contentLength);
writer.WriteLine();
writer.Flush();
Stream output = writer.BaseStream;
Stream input = fileToUpload.OpenRead();
var buffer = new byte[4096];
for (int size = input.Read(buffer, 0, buffer.Length);
size > 0;
size = input.Read(buffer, 0, buffer.Length))
{
output.Write(buffer, 0, size);
}
output.Flush();
writer.WriteLine();
writer.WriteLine(boundaryCompleter);
writer.Flush();
stream.Close();
request.BeginGetResponse(ReadHttpResponseCallback, request);
}, request);
By default, the HttpWebRequest class will automatically follow any redirects. I'm going to suspect that this is causing the issue.
To get around it, set httpWebRequest.AllowAutoRedirect to false. The default is true.
Once the response has been returned, you will then be responsible for checking if the status code is 301, 302, 303 or 307, retrieving the value of the Location header and then executing the redirect yourself. In the case of Silverlight, the redirect would most likely correlate to a page level navigation, so this is something you'd need to take care of manually anyway.
Is it possible to copy the URL of a Record/Document from HP's TRIM and sent it to someone in order to download?
Before TRIM 7, whether you can do this natively depends on which TRIM features are installed.
To see if you have the right stuff, make a TR5 file on your desktop, and rightclick on it - "TryURL" - copy the URL
(the TryURL right click action requires TRIM client stuff - if you don't have that, try opening the TR5 file in notepad, and see if there is a hyperlink in there).
You do get this functionality with the SharePoint connector for TRIM (TIPS or TSCI)
Or there is a cheap third party product that looks cool - from Icognition Pty Ltd.
There are a few ways of going about doing something like this. Assuming you're sending the link to someone on the same WAN, or the security-risky option of having your TRIM system internet accessible, what I'd do is build a simple web service over the top of the TRIM SDK. The TRIM SDK is COM (with a PIA wrapper) or a proper .Net assembly (in version 7.*), so a simple ASP.Net service would be quite easy.
Below is the code for an ASP.Net service I built, based on a code sample provided by HP (based on the TRIMSDKPIA20.dll, not the TRIM 7.0 HP.HPTRIM.SDK assembly). You could use it as a basis to make something more RESTful, but as it is, you'd call it using a URL like:
http://server/ViewRecord/recno=D11-001
You could then create an "External Link", an Addin based again on the SDK that you can register as a Right-Click option in the TRIM interface. Something like "Send Download URL..." that fires up an email and generates the URL, but that's a bit more complicated.
Anyway, the code for the webservice:
using System;
using System.Collections.Generic;
using System.Web;
using System.Web.UI;
using System.Web.UI.WebControls;
using TRIMSDK;
using System.Configuration;
using Microsoft.Win32;
using System.IO;
public partial class _Default : System.Web.UI.Page
{
protected void Page_Load(object sender, EventArgs e)
{
string errormsg = string.Empty;
//Response.Clear();
Response.Cache.SetCacheability(HttpCacheability.NoCache);
Database trim = new Database();
trim.SetAsWebService();
trim.Id = ConfigurationSettings.AppSettings["dbid"];
trim.WorkgroupServerName = ConfigurationSettings.AppSettings["wgserver"];
trim.WorkgroupServerPort = Int32.Parse(ConfigurationSettings.AppSettings["wgport"]);
trim.Connect();
string recno = Request.QueryString["recno"];
if (String.IsNullOrEmpty(recno))
{
errormsg = "No recno parameter was passed.";
}
else
{
Record rec = trim.GetRecord(recno);
if (rec == null)
{
errormsg = string.Format("Could not find a record with number \"{0}\". Either it doesn't exist, or you don't have permission to view it.", recno);
}
else
{
if (!rec.IsElectronic)
{
errormsg = string.Format("Record {0} does not have an electronic attachment", rec.Number);
}
else
{
IStream s = rec.GetDocumentStream(null, false, null);
Response.ClearHeaders();
Response.AddHeader("Content-Disposition", "filename=" + rec.SuggestedFileName);
Response.AddHeader("Content-Length", rec.DocumentSize.ToString());
Response.Buffer = false;
Response.ContentType = GetContentType(rec.Extension);
uint BufferSize = 10000;
uint DocumentLength = (uint)rec.DocumentSize;
byte[] buffer = new byte[BufferSize];
uint bytesread;
uint totalread = 0;
Stream outstream = Response.OutputStream;
while (totalread < DocumentLength)
{
s.RemoteRead(out buffer[0], 10000, out bytesread);
if (bytesread < 10000)
{
for (uint i = 0; i < bytesread; i++)
{
outstream.WriteByte(buffer[i]);
}
}
else
{
outstream.Write(buffer, 0, 10000);
}
totalread += bytesread;
}
outstream.Close();
Response.Flush();
return;
}
}
}
Response.Write(errormsg);
}
private string GetContentType(string fileExtension)
{
string ct = Registry.GetValue(#"HKEY_CLASSES_ROOT\." + fileExtension.ToLower(), "Content Type", string.Empty) as string;
if (ct.Length == 0)
{
ct = "application/octet-stream";
}
return ct;
}
}