Given the generic handler:
<%# WebHandler Language="C#" Class="autocomp" %>
using System;
using System.Text;
using System.Text.RegularExpressions;
using System.Web;
using System.Web.UI;
public class autocomp : IHttpHandler {
public void ProcessRequest (HttpContext context) {
context.Response.ContentType = "application/json";
context.Response.BufferOutput = true;
var searchTerm = (context.Request.QueryString["name_startsWith"] + "").Trim();
context.Response.Write(searchTerm);
context.Response.Write(DateTime.Now.ToString("s"));
context.Response.Flush();
}
public bool IsReusable {
get {
return false;
}
}
}
How would I server side cache this file for 1 hour based on the name_startsWith query string parameter? With web user controls it's easy:
<%# OutputCache Duration="120" VaryByParam="paramName" %>
But I've been looking around for a while to do the same with a generic handler (ashx) file and can't find any solutions.
With the code you've provided you're telling the end user browser to cache the results for 30 minutes, so you aren't doing any server side caching.
If you want to cache the results server side you're probably looking for HttpRuntime.Cache. This would allow you to insert an item into a cache that is globally available. Then on the page load you would want to check the existence of the cached item, then if the item doesn't exist or is expired in the cache, go to the database and retrieve the objects.
EDIT
With your updated code sample, I found https://stackoverflow.com/a/6234787/254973 which worked in my tests. So in your case you could do:
public class autocomp : IHttpHandler
{
public void ProcessRequest(HttpContext context)
{
OutputCachedPage page = new OutputCachedPage(new OutputCacheParameters
{
Duration = 120,
Location = OutputCacheLocation.Server,
VaryByParam = "name_startsWith"
});
page.ProcessRequest(HttpContext.Current);
context.Response.ContentType = "application/json";
context.Response.BufferOutput = true;
var searchTerm = (context.Request.QueryString["name_startsWith"] + "").Trim();
context.Response.Write(searchTerm);
context.Response.Write(DateTime.Now.ToString("s"));
}
public bool IsReusable
{
get
{
return false;
}
}
private sealed class OutputCachedPage : Page
{
private OutputCacheParameters _cacheSettings;
public OutputCachedPage(OutputCacheParameters cacheSettings)
{
// Tracing requires Page IDs to be unique.
ID = Guid.NewGuid().ToString();
_cacheSettings = cacheSettings;
}
protected override void FrameworkInitialize()
{
base.FrameworkInitialize();
InitOutputCache(_cacheSettings);
}
}
}
For multiple query string parameters
public class test : IHttpHandler
{
public void ProcessRequest(HttpContext context)
{
OutputCachedPage page = new OutputCachedPage(new OutputCacheParameters
{
Duration = 120,
Location = OutputCacheLocation.Server,
VaryByParam = "name;city"
});
page.ProcessRequest(HttpContext.Current);
context.Response.ContentType = "application/json";
context.Response.BufferOutput = true;
var searchTerm = (context.Request.QueryString["name"] + "").Trim();
var searchTerm2 = (context.Request.QueryString["city"] + "").Trim();
context.Response.Write(searchTerm+" "+searchTerm2+" ");
context.Response.Write(DateTime.Now.ToString("s"));
}
public bool IsReusable
{
get
{
return false;
}
}
private sealed class OutputCachedPage : Page
{
private OutputCacheParameters _cacheSettings;
public OutputCachedPage(OutputCacheParameters cacheSettings)
{
// Tracing requires Page IDs to be unique.
ID = Guid.NewGuid().ToString();
_cacheSettings = cacheSettings;
}
protected override void FrameworkInitialize()
{
base.FrameworkInitialize();
InitOutputCache(_cacheSettings);
}
}
}
IIS does not use Max Age to cache anything as it is not HTTP PROXY.
That is because you are not setting Last Modified Date Time of some dependent file. IIS needs a cache dependency (file dependency, so that it can check the last update time) and compare it with cache. IIS does not work as HTTP Proxy, so it will not cache items for 30 seconds, instead IIS only updates cache based on some sort of date time or some cache variable.
You can add cache dependency two says, File Dependency and Sql Cache Dependency.
How dynamic caching works in IIS, lets say you have an html file. IIS considers a static html text as cachable file and it will gzip it and put cached copy in its cache. If last update time for static html is older then cache time, then it will use cache. If the file was modified, IIS will find that last update time of html is greater then cache time, so it will reset the cache.
For dynamic content, you have to plan your caching accordingly. If you are serving a content based on some row stored in SQL table, then you should keep track of last update time on that row and add cache dependency on IIS along with SQL to query the last update time of item you are trying to cache.
To cache file, such as .js, .css or other you need to put it to the context.cache. Example:
public void ProcessRequest(HttpContext context)
{
var cachedResult = context.Cache.Get(context.Request.Path);
if (cachedResult != null && cachedResult.GetType() == typeof(VueFileRequestResult))
{
RequestedFileResponce(context, cachedResult as VueFileRequestResult);
return;
}
// SOME ACTIONS WITH NON-CACHED FILE
var fileContent = System.IO.File.ReadAllBytes(filePath);
var result = new VueFileRequestResult(contentType.GetDescription(), fileContent);
RequestedFileResponce(context, result);
var expirationDate = DateTime.Now.AddYears(1);
var dependency = new CacheDependency(filePath);
context.Cache.Add(context.Request.Path, result, dependency, expirationDate, TimeSpan.Zero, CacheItemPriority.Low, null);
return;
}
Related
I have a service method which is getting data from a URL based on the parameters news/blogs.
How to cache the data coming from the URL every 30 minutes?
The user details also get cached for 30 minutes.
Also how do i cache the news,blogs separately if they are coming from the same service URL
[ServiceBehavior(InstanceContextMode = InstanceContextMode.Single)]
public class IITNews : IIITNews
{
System.Web.SessionState.HttpSessionState session = HttpContext.Current.Session;
private static DateTime cacheExpiry = DateTime.MinValue;
string loggedinUser = "";
private string cachedResponse = "";
public Stream getNewsBlogsZD(string type, string noOfItems, string devicetype)
{
try
{
if (cacheExpiry > DateTime.Now)
{
if (System.Web.HttpContext.Current.Cache[cachedResponse] != null)
{
logger.Debug(loggedinUser + "Getting the cached data");
}
return new MemoryStream(Encoding.UTF8.GetBytes(cachedResponse));
}
else
{
string loggedInUserNum = GetEmployeeNumber();
string scenarioType = "";
string url = "";
if (type.Trim().ToUpper() == "NEWS")
{
scenarioType = "DisplayNews";
url = ServiceURL + "getWidgetDetails?WName=News&noOfItems=" + Convert.ToInt32(noOfItems.Trim());
}
else if (type.Trim().ToUpper() == "ZD")
{
scenarioType = "DisplayZD";
url = ServiceURL + "getWidgetDetails?WName=ZD&noOfItems=" + Convert.ToInt32(noOfItems.Trim());
}
else if (type.Trim().ToUpper() == "BLOGS")
{
scenarioType = "DisplayBlogs";
url = ServiceURL + "getWidgetDetails?WName=Blogs&noOfItems=" + Convert.ToInt32(noOfItems.Trim());
}
WebClient client = new WebClient();
client.UseDefaultCredentials = true;
client.Encoding = System.Text.Encoding.UTF8;
cachedResponse = client.DownloadString(url);
HttpContext.Current.Cache.Insert("cacheResponse",cachedResponse ,null);
cacheExpiry = DateTime.Now.AddMinutes(30);
return new MemoryStream(Encoding.UTF8.GetBytes(cachedResponse));
}
}
catch (Exception ex)
{
string message=ex.message;
return new MemoryStream(Encoding.UTF8.GetBytes("[]"));
}
}
public stream userdertails
{
//method to fetch user details where no cache is used
}
}
If this is your service, you can cache it on the service and return same data for very same parameters.
If not, you can create some singleton class to hold data in. If the singleton has valid data, return them instead of calling WebService :)
Store the result somewhere, along with a time stamp. Where you store it is up to you. There are a variety of caching systems you can find, there's a database, there's the file system, in-memory static values, instance values, etc., etc.
Possibly the simplest approach would just be a class-level value. Something like this:
private string cachedResponse = "";
private DateTime cacheExpiry = DateTime.MinValue;
Then in your logic you would check for the cache before deciding to query the service. Something like this:
if (cacheExpiry > DateTime.Now)
return new MemoryStream(Encoding.UTF8.GetBytes(cachedResponse));
// the rest of your method to query the service
// then...
cachedResponse = client.DownloadString(url);
cacheExpiry = DateTime.Now.AddMinutes(30);
return new MemoryStream(Encoding.UTF8.GetBytes(cachedResponse));
This is just an example of the logic behind the concept of a cache, of course. If your object instance itself doesn't remain in memory then this object-level cache of course wouldn't do the job. The logic is always the same:
Check if the cache is still valid.
If so, return the cached value.
If not, query the service and store the new value and update the cache expiry and return the newly cached value.
Where you store the cache is up to you and depends on more than what's present in this question.
You can use the MemoryCache Class which enables you to cache objects and set time of them to live in cache.
I'm using asp.net and c# for backend code and html, javascript/jquery on frontend
User clicks "Details" on website to view and edit requests previously created.
jquery/ajax calls method on backend to get infomation about requests.
An object, newWaferRequest, is created which holds this information. The object is returned from ajax call and the site populates a form with that information. The user can then edit the form, add new programs to requests, modify existing requests info etc...
The problem I'm getting is when the user selects "Save pgm", ajax calls backend savePgm method which is supposed to update the Object newWaferRequest previously created, but I'm receiving a nullReferenceException. The exception is pointing to the newWaferRequest object being null. This happens at different times when the user is updating the request. Sometimes the user can update the program and add new programs, with a correct response from backend. Other times the exception occurs.
I must not have a full grasp of how variables are "stored" when using the web. I read multiple post on here and have read about Session and ViewState variables. I've tried saving the Object in a Session with no luck.
This is just showing the beginning of my c# code, I tried using static variables initially. All the objects are in App_code cs files.
public partial class Nbump_Request : System.Web.UI.Page
{
//public static person currentPerson = new person();
//public static requestSearch searchRequest;
//public static waferRequest newWaferRequest;
//public static waferRequestObject newWaferRequestObject;
static person currentPerson = new person();
static requestSearch searchRequest;
static waferRequest newWaferRequest;
static waferRequestObject newWaferRequestObject;
Here is ajax method for existing requests, which creates objects
[WebMethod]
public static string queryRequestExisting(string request_pk)
{
JavaScriptSerializer js = new JavaScriptSerializer();
try
{
newWaferRequest = new waferRequest(request_pk, "EXISTINGREQUEST");
newWaferRequestObject = newWaferRequest.CURRENTREQUEST;
string json = js.Serialize(newWaferRequest);
return json;
}
catch (Exception)
{
return null;
}
}
Here is ajax code for when user saves or updates program information. I added in the code to check and return with the object was null.
public static string savePgmData(Dictionary<string, string> pgm_requests)
{
PgmRequestObject currentPgm;
if (newWaferRequest != null)
{
try
{
//if (!Convert.ToBoolean(pgm_requests["pgmEdit"])) //not performed when request pgm being edited
string rtnMessage;
//create new pgm in waferRequest object
if (pgm_requests["PGMRT_INDEX_PK"] == null)
{
int index = newWaferRequest.addNewPGMRequest() - 1;
currentPgm = (PgmRequestObject)newWaferRequest.PGMREQUESTLIST[index];
string pgmPrimaryKey = currentPgm.PGMRT_INDEX_PK;
rtnMessage = "{\"message\": \"Added and saved new pgm successfully\", \"primaryKey\":\"" + pgmPrimaryKey + "\"}";
}
//get pgm to update at index provided
else if (pgm_requests["pgmRowIndex"] != "-1")
{
int index = Convert.ToInt32(pgm_requests["pgmRowIndex"]);
currentPgm = (PgmRequestObject)newWaferRequest.PGMREQUESTLIST[index];
rtnMessage = "Edited and saved pgm successfully";
}
//save initial pgm
else
{
currentPgm = (PgmRequestObject)newWaferRequest.PGMREQUESTLIST[0];
rtnMessage = "Initial pgm added successfully";
}
currentPgm.updatePGM(pgm_requests["pgmNameList"], pgm_requests["pgmNameText"]);
currentPgm.updateStatus(pgm_requests["pgmStatus"]);
currentPgm.updateWafers(pgm_requests["pgmWfrList"], pgm_requests["pgmWfrNum"]);
currentPgm.updatePriority(pgm_requests["pgmTestOrder"]);
currentPgm.updateFailBinRetest(pgm_requests["pgmFbr"], pgm_requests["pgmFbrBins"]);
//currentPgm.CommitIntoDatabase(); // -- Called on individual requests updates before request saved
return rtnMessage;
}
catch (Exception ex)
{
return "Failed to save program\n\n" + ex.Data + "\n" + ex.Message + "\n" + ex.StackTrace + "\n";
}
}
else
{
return "newWaferRequest object is null\n";
}
}
This is a short snippet of the class for waferRequest. This is located in the app_code and is a cs file. The pgmRequestList contains an array of pgm objects. That is what I update when user selects "Save Program".
public class waferRequest
{
/*The class will hold request information, creation and delete.
*
*/
private waferRequestObject currentRequest;
private ArrayList WaferRequestList = new ArrayList();
private ArrayList PGMRequestList = new ArrayList();
Here is an example of what I tried for Session. I got rid of static variables and modified the methods
[WebMethod(EnableSession=true)]
public static string queryRequestExisting(string request_pk)
{
waferRequest newWaferRequest;
waferRequestObject newWaferRequestObject;
JavaScriptSerializer js = new JavaScriptSerializer();
try
{
newWaferRequest = new waferRequest(request_pk, "EXISTINGREQUEST");
newWaferRequestObject = newWaferRequest.CURRENTREQUEST;
HttpContext.Current.Session["newWaferRequest"] = newWaferRequest;
HttpContext.Current.Session["newWaferRequestObject"] = newWaferRequestObject;
string json = js.Serialize(newWaferRequest);
return json;
}
catch (Exception)
{
return null;
}
}
//snippet of savePgmData with session
public static string savePgmData(Dictionary<string, string> pgm_requests)
{
waferRequest newWaferRequest = HttpContext.Current.Session["newWaferRequest"];
PgmRequestObject currentPgm;
if (newWaferRequest != null)
{
try
{
//if (!Convert.ToBoolean(pgm_requests["pgmEdit"])) //not performed when request pgm being edited
string rtnMessage;
//create new pgm in waferRequest object
if (pgm_requests["PGMRT_INDEX_PK"] == null)
{
int index = newWaferRequest.addNewPGMRequest() - 1;
currentPgm = (PgmRequestObject)newWaferRequest.PGMREQUESTLIST[index];
I am limited to the code I'm allowed to show because of work security. Please let me know if more information is needed.
Also, the code seems to work fine on my local server. The issue is when I put it on the production server or development server -- Both of which I don't have access to make changes on.
Before beginning this question, I should point out that my knowledge of ASP.NET & C# is pretty much nil.
I'm in the process of trying to integrate the ASP.NET version of CKFinder v3 into a site built in a different language and all is going well so far; I have everything setup as I want it and it's working when I grant unrestricted access to CKF but I'm stuck at the point now of trying to restrict access to it by authenticating only certain members of my site to use it. All the pages that CKFinder appears on on my site are only accessible by those certain members but I need an extra level of security if, for example, anyone figures out the direct path to my "ckfinder.html" file.
In the ASP version of CKFinder, I simply added this line in the function that checks my member's privileges, where isEditor was a boolean whose value was assigned per member based on information from my database:
session("accessckf")=isEditor
and then edited the CheckAuthentication() function in CKFinder's "config.asp" file to read:
function CheckAuthentication()
CheckAuthentication=session("accessckf")
end function
Reading through this "Howto", authentication seems to be much more complex in v3 but, after a lot of trial and error and some help from Lesiman, I created this C# file, which is located in my CKF directory:
<%#page codepage="65001" debug="true" language="c#" lcid="6153"%>
<%#import namespace="CKSource.CKFinder.Connector.Core"%>
<%#import namespace="CKSource.CKFinder.Connector.Core.Authentication"%>
<%#import namespace="CKSource.CKFinder.Connector.Core.Builders"%>
<%#import namespace="CKSource.CKFinder.Connector.Host.Owin"%>
<%#import namespace="Owin"%>
<%#import namespace="System.Data.Odbc"%>
<%#import namespace="System.Threading"%>
<%#import namespace="System.Threading.Tasks"%>
<script runat="server">
public void Configuration(IAppBuilder appBuilder){
var connectorBuilder=ConfigureConnector();
var connector=connectorBuilder.Build(new OwinConnectorFactory());
appBuilder.Map("/path/to/connector",builder=>builder.UseConnector(connector));
}
public ConnectorBuilder ConfigureConnector(){
var connectorBuilder=new ConnectorBuilder();
connectorBuilder.SetAuthenticator(new MyAuthenticator());
return connectorBuilder;
}
public class MyAuthenticator:IAuthenticator{
public Task<IUser> AuthenticateAsync(ICommandRequest commandRequest,CancellationToken cancellationToken){
var domain=HttpContext.Current.Request.Url.Host;
var cookie=HttpContext.Current.Request.Cookies[urlDomain];
var password="";
var username="";
var user=new User(false,null);
if (cookie!=null){
if (cookie["username"]!=null)
username=cookie["username"];
if (cookie["password"]!=null)
password=cookie["password"];
if(username!=""&&password!=""){
var connection=new OdbcConnection("database=[database];driver=MySQL;pwd=[pwd];server=[server];uid=[uid];");
connection.Open();
OdbcDataReader records=new OdbcCommand("SELECT ISEDITOR FROM MEMBERS WHERE USERNAME='"+username+"' AND PASSWORD='"+password+"'",connection).ExecuteReader();
if(records.HasRows){
records.Read();
bool isEditor=records.GetString(0)=="1";
var roles="member";
if(isEditor)
roles="editor,member";
user=new User(isEditor,roles.Split(','));
}
records.Close();
connection.Close();
}
}
return Task.FromResult((IUser)user);
}
}
</script>
Loading that page produces no errors (which doesn't necessarily mean it's working as trying to write anything to screen from within the public class doesn't work, for some reason) so now I'm at the stage of somehow checking that file for authentication.
Originally, I had tried loading it via XMLHttp from within my function that checks membership privileges for the site but, as I suspected and as Lesmian confirmed, that wouldn't work. After more trial & error, I added code to check website member privileges to the C# file, which leads me to where I am now: stuck!
What do I need to edit within CKFinder in order to have it use this custom file to check whether or not a user is authenticated?
First you'll need a connector between the ASP's Session and CKFinder's .Net authenticator. Here's an example that serializes ASP Session contents into JSON.
Put the connector.asp into a publicly accessible location. http://myaspwebsite.com/connector.asp for example.
connector.asp
<%#Language=VBScript CodePage=65001%>
<% Option Explicit %>
<!--#include file="JSON.asp"-->
<%
' obtain JSON.asp from https://github.com/tugrul/aspjson/archive/master.zip
' just for testing, must be removed in the production environment
Session("isEditor") = True
Session("isMember") = True
' only local requests allowed
' instead of local and remote ip comparison, a secret key can be used
If Request.ServerVariables("LOCAL_ADDR") <> Request.ServerVariables("REMOTE_ADDR") Then
Response.Status = "403 Forbidden"
Response.End
End If
Response.ContentType = "application/json"
Response.Charset = "utf-8"
Dim JSONObject, Key
Set JSONObject = jsObject()
For Each Key In Session.Contents
If Not IsObject(Session.Contents(Key)) Then 'skip the objects cannot be serialized
JSONObject(Key) = Session.Contents(Key)
End If
Next
JSONObject.Flush
%>
CKFinder 3.3.0 comes with a default connector which can be found in /ckfinder/bin/CKSource.CKFinder.Connector.WebApp.dll, remove it.
Examine the following program and remember to replace builder.Map("/connector", SetupConnector); and new Uri("http://myaspwebsite.com/connector.asp"); with your own values.
It simply authenticates the users by checking ASP Session varaibles isEditor and isMember via connector.asp and finally claims the roles editor , member or none.
I assume that you have configured the roles editor and member in the web.config.
Then put the Shaggy.cs into /ckfinder/App_Code. Create App_Code directory if not exist. .Net files in this folder will be compiled on the fly.
For more information have a look at Shared Code Folders in ASP.NET Web Projects
Shaggy.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Owin.Security;
using Microsoft.Owin.Security.Cookies;
using Newtonsoft.Json.Linq;
using Owin;
[assembly: Microsoft.Owin.OwinStartup(typeof(CKSource.CKFinder.Connector.Shaggy.Startup))]
namespace CKSource.CKFinder.Connector.Shaggy
{
using FileSystem.Local;
using FileSystem.Dropbox;
using Core;
using Core.Authentication;
using Config;
using Core.Builders;
using Core.Logs;
using Host.Owin;
using Logs.NLog;
using KeyValue.EntityFramework;
public class Startup
{
public void Configuration(IAppBuilder builder)
{
LoggerManager.LoggerAdapterFactory = new NLogLoggerAdapterFactory();
RegisterFileSystems();
builder.UseCookieAuthentication(new CookieAuthenticationOptions
{
AuthenticationType = "ApplicationCookie",
AuthenticationMode = AuthenticationMode.Active
});
//replace connector path with yours
builder.Map("/connector", SetupConnector);
}
private static void RegisterFileSystems()
{
FileSystemFactory.RegisterFileSystem<LocalStorage>();
FileSystemFactory.RegisterFileSystem<DropboxStorage>();
}
private static void SetupConnector(IAppBuilder builder)
{
var keyValueStoreProvider = new EntityFrameworkKeyValueStoreProvider("CacheConnectionString");
var authenticator = new ShaggysAuthenticator();
var connectorFactory = new OwinConnectorFactory();
var connectorBuilder = new ConnectorBuilder();
var connector = connectorBuilder
.LoadConfig()
.SetAuthenticator(authenticator)
.SetRequestConfiguration(
(request, config) =>
{
config.LoadConfig();
config.SetKeyValueStoreProvider(keyValueStoreProvider);
})
.Build(connectorFactory);
builder.UseConnector(connector);
}
}
public class ShaggysAuthenticator : IAuthenticator
{
// this method makes an http request on the background to gather ASP's all session contents and returns a JSON object
// if the request contains ASP's session cookie(s)
private static JObject GetAspSessionState(ICommandRequest requestContext)
{
// building Cookie header with ASP's session cookies
var aspSessionCookies = string.Join(";",
requestContext.Cookies.Where(cookie => cookie.Key.StartsWith("ASPSESSIONID"))
.Select(cookie => string.Join("=", cookie.Key, cookie.Value)));
if (aspSessionCookies.Length == 0)
{
// logs can be found in /ckfinder/App_Data/logs
LoggerManager.GetLoggerForCurrentClass().Info("No ASP session cookie found");
// don't make an extra request to the connector.asp, there's no session initiated
return new JObject();
}
//replace this URL with your connector.asp's
var publicAspSessionConnectorUrl = new Uri("http://myaspwebsite.com/connector.asp");
var localSafeAspSessionConnectorUrl = new UriBuilder(publicAspSessionConnectorUrl) { Host = requestContext.LocalIpAddress };
using (var wCli = new WebClient())
try
{
wCli.Headers.Add(HttpRequestHeader.Cookie, aspSessionCookies);
wCli.Headers.Add(HttpRequestHeader.Host, publicAspSessionConnectorUrl.Host);
return JObject.Parse(wCli.DownloadString(localSafeAspSessionConnectorUrl.Uri));
}
catch (Exception ex) // returning an empty JObject object in any fault
{
// logs can be found in /ckfinder/App_Data/logs
LoggerManager.GetLoggerForCurrentClass().Error(ex);
return new JObject();
}
}
public Task<IUser> AuthenticateAsync(ICommandRequest commandRequest, CancellationToken cancellationToken)
{
var aspSessionState = GetAspSessionState(commandRequest);
var roles = new List<string>();
var isEditor = aspSessionState.GetNullSafeValue("isEditor", false);
var isMember = aspSessionState.GetNullSafeValue("isMember", false);
if (isEditor) roles.Add("editor");
if (isMember) roles.Add("member");
var isAuthenticated = isEditor || isMember;
var user = new User(isAuthenticated, roles);
return Task.FromResult((IUser)user);
}
}
public static class JObjectExtensions
{
// an extension method to help case insensitive lookups with a default value to get avoid NullReferenceException
public static T GetNullSafeValue<T>(this JObject jobj, string key, T defaultValue = default(T))
{
dynamic val = jobj.GetValue(key, StringComparison.OrdinalIgnoreCase);
if (val == null) return defaultValue;
return (T)val;
}
}
}
Now you should have a working CKFinder connector. Change the logic in the method AuthenticateAsync if you need and see how CKFinder handles your Classic ASP membership management.
Did you setup your custom authentication provider with ConnectorBuilder?
public ConnectorBuilder ConfigureConnector()
{
var connectorBuilder = new ConnectorBuilder();
connectorBuilder.SetAuthenticator(new MyAuthenticator());
return connectorBuilder;
}
You can find full example here: http://docs.cksource.com/ckfinder3-net/configuration_by_code.html.
UPDATE
Additionally you should register ConnectorBuilder inside Startup class to add it to request pipeline:
public void Configuration(IAppBuilder appBuilder)
{
var connectorBuilder = ConfigureConnector();
var connector = connectorBuilder.Build(new OwinConnectorFactory());
appBuilder.Map("/CKFinder/connector", builder => builder.UseConnector(connector));
}
All this is from a documentation link I've provided before.
I have a view that is, instead of returning a View(), is returning a dynamically created PDF and then showing the PDF in a new tab. I'm not saving the PDF anywhere, or storing it anywhere. What I would like to do is have a loading screen show up while the PDF is being created. Can this be done?
public ActionResult SolicitorActionReport_Load(SolicitorActionParamsViewModel viewModel) {
var cultivationModel = new CultivationModel(viewModel, ConstituentRepository, CampaignRepository);
var cultivationData = cultivationModel.GetCultivationActivityData();
var reportParamModel = new List<ReportParamModel>
{new ReportParamModel {AgencyName = SelectedUserAgency.AgencyName, StartDate = viewModel.StartDate, EndDate = viewModel.EndDate}};
var reportToRun = "ActionDateCultivationReport";
if (viewModel.SortActionBy == SolicitorActionReportSortType.Constituent) {
reportToRun = "ConstituentCultivationReport";
} else if (viewModel.SortActionBy == SolicitorActionReportSortType.Solicitor) {
reportToRun = "SolicitorCultivationReport";
}
return FileContentPdf("Constituent", reportToRun, cultivationData, reportParamModel, new List<FundraisingAppealMassSummary>(), new List<FundraisingAppealPortfolioSummary>());
}
public FileContentResult FileContentPdf(string folder, string reportName, object dataSet,object reportParamModel,object appealMassDataSet, object appealPortfolioDataSet) {
var localReport = new LocalReport();
localReport.ReportPath = Server.MapPath("~/bin/Reports/" + folder + "/rpt" + reportName + ".rdlc");
var reportDataSource = new ReportDataSource(reportName + "DataSet", dataSet);
var reportParamsDataSource = new ReportDataSource("ReportParamModelDataSet", reportParamModel);
var reportParamsDataSourceMass = new ReportDataSource("FundraisingAppealMassSummaryDataSet", appealMassDataSet);
var reportParamsDataSourcePortfolio = new ReportDataSource("FundraisingAppealPortfolioSummaryDataSet", appealPortfolioDataSet);
#region Setting ReportViewControl
localReport.DataSources.Add(reportDataSource);
localReport.DataSources.Add(reportParamsDataSource);
localReport.DataSources.Add(reportParamsDataSourceMass);
localReport.DataSources.Add(reportParamsDataSourcePortfolio);
localReport.SubreportProcessing += (s, e) => { e.DataSources.Add(reportDataSource); };
string reportType = "pdf";
string mimeType;
string encoding;
string fileNameExtension;
//The DeviceInfo settings should be changed based on the reportType
//http://msdn2.microsoft.com/en-us/library/ms155397.aspx
string deviceInfo = "<DeviceInfo><OutputFormat>PDF</OutputFormat></DeviceInfo>";
Warning[] warnings;
string[] streams;
byte[] renderedBytes;
//Render the report
renderedBytes = localReport.Render(reportType, deviceInfo, out mimeType, out encoding, out fileNameExtension, out streams, out warnings);
#endregion
return File(renderedBytes, mimeType);
}
I'm not saving the PDF anywhere, or storing it anywhere. What I would like to do is have a loading screen show up while the PDF is being created. Can this be done?
Short Answer
No, not in a new tab.
The main problem with what you're trying to do is the lack of power you have when it comes to controlling the browser. Specifically, when you tell an anchor to open its hyperlink in a new tab (ie target="_blank"). There are hacky ways around this that generally are just going to frustrate your user because you're changing behavior that they might be dependent/relying on.
Workaround
You can get very close to your desired outcome by using this jQuery File Download plugin (view a demo). Basically, it manipulates an iframe to queue a download. This makes it possible to show a loading div while also keeping the user on the active page (not directing them to another tab). Then, the user can click the downloaded PDF which will most-likely open in a new tab (view compatible browsers here).
If you decide to use this plugin, here are the steps to applying it:
Download the plugin js source and include it in your Scripts.
Include the FileDownloadAttribute class provided in the plugin MVC Demo:
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, Inherited = true, AllowMultiple = false)]
public class FileDownloadAttribute: ActionFilterAttribute
{
public FileDownloadAttribute(string cookieName = "fileDownload", string cookiePath = "/")
{
CookieName = cookieName;
CookiePath = cookiePath;
}
public string CookieName { get; set; }
public string CookiePath { get; set; }
/// <summary>
/// If the current response is a FileResult (an MVC base class for files) then write a
/// cookie to inform jquery.fileDownload that a successful file download has occured
/// </summary>
/// <param name="filterContext"></param>
private void CheckAndHandleFileResult(ActionExecutedContext filterContext)
{
var httpContext = filterContext.HttpContext;
var response = httpContext.Response;
if (filterContext.Result is FileResult)
//jquery.fileDownload uses this cookie to determine that a file download has completed successfully
response.AppendCookie(new HttpCookie(CookieName, "true") { Path = CookiePath });
else
//ensure that the cookie is removed in case someone did a file download without using jquery.fileDownload
if (httpContext.Request.Cookies[CookieName] != null)
{
response.AppendCookie(new HttpCookie(CookieName, "true") { Expires = DateTime.Now.AddYears(-1), Path = CookiePath });
}
}
public override void OnActionExecuted(ActionExecutedContext filterContext)
{
CheckAndHandleFileResult(filterContext);
base.OnActionExecuted(filterContext);
}
}
github source
Apply the FileDownload attribute to your ActionResult method:
[FileDownload]
public ActionResult SolicitorActionReport_Load(SolicitorActionParamsViewModel viewModel) {
...
return FileContentPdf("Constituent", reportToRun, cultivationData, reportParamModel, new List<FundraisingAppealMassSummary>(), new List<FundraisingAppealPortfolioSummary>());
}
Include the necessary markup in the View to which you'll be linking to the report:
<a class="report-download" href="/Route/To/SolicitorActionReport">Download PDF</a>
Attach an event handler to the report-download anchor:
$(document).on("click", "a.report-download", function () {
$.fileDownload($(this).prop('href'), {
preparingMessageHtml: "We are preparing your report, please wait...",
failMessageHtml: "There was a problem generating your report, please try again."
});
return false; //this is critical to stop the click event which will trigger a normal file download!
});
You can view working demos at http://jqueryfiledownload.apphb.com/. There is also a demo that uses pre-styled jQuery UI modals to "prettify" the user experience.
You can also download the demo ASP.NET MVC solution from johnculviner / jquery.fileDownload github to see all of this working.
I think you have two choices:
Redirect to a "loading" page with fancy GIF spinners, then direct the request to the PDF (this would work if the PDF take a little server time to generate - the visitor would be looking at a loading page while waiting for next page to load)
or
Use an iFrame: load a page that has an iframe. This page can overlay a spinning GIF and loading message while the iFrame loads the PDF itself. Note: you could make the iframe 100% width and height
I have restricted access to a site by using Integrated Windows Authentication and turning off anonymous access. This way I can then show them their real name (from looking up on Active Directory and using the server variable LOGON_USER) and do other related Active Directory tasks.
How can I then prompt again for their user credentials, through a 'sign in as other user' link , showing the browser prompt (like you would get on a browser like Chrome or Firefox, or if the site was not in the 'Intranet' zone in IE) rather than a Web Form?
Since SharePoint offers this functionality, I assume there is a way to do this through code, but I don't know what code can do this (using C#). I can send a 401 header which makes the prompt appear, but how do you then confirm if they are logged in?
Maybe this can help you out.
ASP .NET – C# – How to “Sign in as Different User” like in Microsoft SharePoint with Windows Authentication
Try this approach. It is based on disassembled code of the method Microsoft.SharePoint.ApplicationPages.AccessDeniedPage.LogInAsAnotherUser()
First of all, I'm accessing the AccessDeniedPage page using javascript because Sharepoint does something similar:
function GoToSignAs() {
window.location.replace("./SignAs.aspx?signAs=true&returnUrl=" + window.location.toString());
}
<a onclick="GoToSignAs(); return false;" href="javascript:;">SignAs</a>
Then, in your page AccessDeniedPage you use this:
public partial class SignAs : Page
{
private const string LoginAttempts = "LoginAttempts";
protected override void OnLoad(EventArgs e)
{
base.OnLoad(e);
HttpContext current = HttpContext.Current;
if (current == null)
{
throw new InvalidOperationException();
}
if (GetUrlParameter<bool>("signAs"))
{
HandleSignAs(current, GetUrlParameter<string>("returnUrl"));
}
}
// ...
private static void HandleSignAs(HttpContext context, string returnUrl)
{
int attempts = 0;
HttpCookie attemptsCookie = context.Request.Cookies[LoginAttempts];
if (attemptsCookie == null || string.IsNullOrEmpty(attemptsCookie.Value))
{
attemptsCookie = new HttpCookie(LoginAttempts);
}
else
{
attempts = int.Parse(attemptsCookie.Value, CultureInfo.InvariantCulture);
}
if (!string.IsNullOrEmpty(context.Request.Headers["Authorization"]))
{
// Attempts are counted only if an authorization token is informed.
attempts++;
}
if (attempts>1)
{
attemptsCookie.Value = string.Empty;
context.Response.Cookies.Add(attemptsCookie);
context.Response.Redirect(returnUrl, true);
}
else
{
attemptsCookie.Value = attempts.ToString(CultureInfo.InvariantCulture);
context.Response.Cookies.Add(attemptsCookie);
SendEndResponse(context, 401, "401 Unauthorized");
}
}
private static void SendEndResponse(HttpContext context, int code, string description)
{
HttpResponse response = context.Response;
context.Items["ResponseEnded"] = true;
context.ClearError();
response.StatusCode = code;
response.Clear();
response.StatusDescription = description;
response.AppendHeader("Connection", "close");
response.AddHeader("WWW-Authenticate", "Negotiate");
response.AddHeader("WWW-Authenticate", "NTLM");
response.End();
}
}
FIX: you must use the IIS to work properly