DNN 6 Module - How to leverage asynchronous calls - c#

DotNetNuke 6 does not appear to support WebMethods due to modules being developed as user controls, not aspx pages.
What is the recommended way to route, call and return JSON from a DNN user module to a page containing that module?

It appears the best way to handle this problem is custom Httphandlers. I used the example found in Chris Hammonds Article for a baseline.
The general idea is that you need to create a custom HTTP handler:
<system.webServer>
<handlers>
<add name="DnnWebServicesGetHandler" verb="*" path="svc/*" type="Your.Namespace.Handler, YourAssembly" preCondition="integratedMode" />
</handlers>
</system.webServer>
You also need the legacy handler configuration:
<system.web>
<httpHandlers>
<add verb="*" path="svc/*" type="Your.Namespace.Handler, YourAssembly" />
</httpHandlers>
</system.web>
The handler itself is very simple. You use the request url and parameters to infer the necessary logic. In this case I used Json.Net to return JSON data to the client.
public class Handler: IHttpHandler
{
public void ProcessRequest(HttpContext context)
{
//because we're coming into a URL that isn't being handled by DNN we need to figure out the PortalId
SetPortalId(context.Request);
HttpResponse response = context.Response;
response.ContentType = "application/json";
string localPath = context.Request.Url.LocalPath;
if (localPath.Contains("/svc/time"))
{
response.Write(JsonConvert.SerializeObject(DateTime.Now));
}
}
public bool IsReusable
{
get { return true; }
}
///<summary>
/// Set the portalid, taking the current request and locating which portal is being called based on this request.
/// </summary>
/// <param name="request">request</param>
private void SetPortalId(HttpRequest request)
{
string domainName = DotNetNuke.Common.Globals.GetDomainName(request, true);
string portalAlias = domainName.Substring(0, domainName.IndexOf("/svc"));
PortalAliasInfo pai = PortalSettings.GetPortalAliasInfo(portalAlias);
if (pai != null)
{
PortalId = pai.PortalID;
}
}
protected int PortalId { get; set; }
}
A call to http://mydnnsite/svc/time is properly handled and returns JSON containing the current time.

does anyone else have an issue of accessing session state/updating user information via this module? I got the request/response to work, and i can access DNN interface, however, when i try to get the current user, it returns null; thus making it impossible to verify access roles.
//Always returns an element with null parameters; not giving current user
var currentUser = UserController.Instance.GetCurrentUserInfo();

Related

ASP.NET Directory Authentication

I have a .net 2.0 application using Forms Authentication with AD and have a directory for documents which has been configured using a web.config file -
<system.web>
<authorization>
<deny users="?"/>
<allow roles="Security Alerts - Admin"/>
<deny users="*"/>
</authorization>
</system.web>
When testing locally if I run the app and put the FQDN for a document /site/documents/Document1.pdf I am returned to the login page but when I have the site on a server I am able to open the PDFs without any problem. How can I force this so that if a user was to saves the URL of a document and tried to access it directly they would be forced to the login page to authenticate themselves first?
I have the same config for an ADMIN folder which includes aspx pages and works correctly and directs the users the Login page first, is it something to do with the doc type being a pdf as opposed to aspx pages.
Thanks in advance.
By default, .NET authentication does not work on static files such as pdfs.
You need to implement an HTTP Handler to serve your files if the user is authenticated.
It sound like your current authentication is set up and working correctly, so I won't go over the basics of setting that up.
Below is the relevant code which applies to your scenario taken from Kory Becker's helpful article here:
http://www.primaryobjects.com/2009/11/11/securing-pdf-files-in-asp-net-with-custom-http-handlers
You'll obviously have to alter the paths, namespaces and logic to suit your environment (e.g. IIS version) and/or specific file type requirements.
Step 1 - Create a FileProtectionHandler class which implements IHttpHandler
public class FileProtectionHandler : IHttpHandler
{
public void ProcessRequest(HttpContext context)
{
switch (context.Request.HttpMethod)
{
case "GET":
{
// Is the user logged-in?
if (!context.User.Identity.IsAuthenticated)
{
FormsAuthentication.RedirectToLoginPage();
return;
}
string requestedFile = context.Server.MapPath(context.Request.FilePath);
// Verify the user has access to the User role.
if (context.User.IsInRole("Security Alerts - Admin"))
{
SendContentTypeAndFile(context, requestedFile);
}
else
{
// Deny access, redirect to error page or back to login page.
context.Response.Redirect("~/User/AccessDenied.aspx");
}
break;
}
}
}
public bool IsReusable { get; private set; }
private HttpContext SendContentTypeAndFile(HttpContext context, String strFile)
{
context.Response.ContentType = GetContentType(strFile);
context.Response.TransmitFile(strFile);
context.Response.End();
return context;
}
private string GetContentType(string filename)
{
// used to set the encoding for the reponse stream
string res = null;
FileInfo fileinfo = new FileInfo(filename);
if (fileinfo.Exists)
{
switch (fileinfo.Extension.Remove(0, 1).ToLower())
{
case "pdf":
{
res = "application/pdf";
break;
}
}
return res;
}
return null;
}
}
Step 2 - Add the following sections to your web.config file (with appropriate path/namespace modifications)
<httpHandlers>
...
<add path="*/User/Documents/*.pdf" verb="*" validate="true" type="CustomFileHandlerDemo.Handlers.FileProtectionHandler" />
</httpHandlers>
<system.webServer>
...
<handlers>
<add name="PDF" path="*.pdf" verb="*" type="CustomFileHandlerDemo.Handlers.FileProtectionHandler" resourceType="Unspecified" />
...
</handlers>
</system.webServer>

Enable CORS for Web API 1, .net 4.0

I need to enable CORS for my Web API and I can't upgrade to Framework 4.5 at the moment. (I know about System.Web.Http.Cors.EnableCorsAttribute.)
I've tried to add the following to my Web.config to see if it worked, but it didn't:
<system.webServer>
<httpProtocol>
<customHeaders>
<add name="Access-Control-Allow-Origin" value="*"/>
</customHeaders>
</httpProtocol>
I've also tried to set the Access-Control-Allow-Origin header to "*" manually by use of System.Web.Http.Filters.ActionFilterAttribute (based on this post: Add custom header to all responses in Web API) - but that didn't work out either as the request is rejected before it gets to the action filtering.
So I'm kinda stuck now.. Any help is appreciated.
Edit: Turns out
<add name="Access-Control-Allow-Origin" value="*"/>
was the answer all along, I must've done something wrong previously when I tested it. But this solution means that all actions are CORS enabled (which will do for now).
POST, PUT, DELETE, etc use pre-flighted CORS. The browser sends an OPTIONS request. This is because browser first, checks if serverside can handle CORS or not using OPTIONS request, if succeeds, then sends actual request PUT or POST or Delete. Since you do not have an action method that handles OPTIONS, you are getting a 405. In its most simplest form, you must implement an action method like this in your controller.
More explanation - http://www.w3.org/TR/cors/#resource-preflight-requests
http://www.html5rocks.com/en/tutorials/cors/
public HttpResponseMessage Options()
{
var response = new HttpResponseMessage();
response.StatusCode = HttpStatusCode.OK;
return response;
}
Note: This this action just responds to OPTION request, so along with this you need to add necessary config to web.config, such as Access-Control-Allow-Origin = * and Access-Control-Allow-Methods = POST,PUT,DELETE.
Web API 2 has CORS support, but with Web API 1, you have to follow this path.
try to add also:
<add name="Access-Control-Allow-Headers" value="*" />
I had faced the lot of issue with webAPI 1 Cross domain access finally able to fix it have a look at my blog http://keerthirb.blogspot.in/2017/08/making-cross-enable-for-webapi1.html
Cross code is
public class CorsHandler : DelegatingHandler
{
const string Origin = "Origin";
const string AccessControlRequestMethod = "Access-Control-Request-Method";
const string AccessControlRequestHeaders = "Access-Control-Request-Headers";
const string AccessControlAllowOrigin = "Access-Control-Allow-Origin";
const string AccessControlAllowMethods = "Access-Control-Allow-Methods";
const string AccessControlAllowHeaders = "Access-Control-Allow-Headers";
protected override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
{
bool isCorsRequest = request.Headers.Contains(Origin);
bool isPreflightRequest = request.Method == HttpMethod.Options;
if (isCorsRequest)
{
if (isPreflightRequest)
{
return Task.Factory.StartNew<HttpResponseMessage>(() =>
{
HttpResponseMessage response = new HttpResponseMessage(HttpStatusCode.OK);
response.Headers.Add(AccessControlAllowOrigin, request.Headers.GetValues(Origin).First());
string accessControlRequestMethod = request.Headers.GetValues(AccessControlRequestMethod).FirstOrDefault();
if (accessControlRequestMethod != null)
{
response.Headers.Add(AccessControlAllowMethods, accessControlRequestMethod);
}
string requestedHeaders = string.Join(", ", request.Headers.GetValues(AccessControlRequestHeaders));
if (!string.IsNullOrEmpty(requestedHeaders))
{
response.Headers.Add(AccessControlAllowHeaders, requestedHeaders);
}
return response;
}, cancellationToken);
}
else
{
return base.SendAsync(request, cancellationToken).ContinueWith<HttpResponseMessage>(t =>
{
HttpResponseMessage resp = t.Result;
resp.Headers.Add(AccessControlAllowOrigin, request.Headers.GetValues(Origin).First());
return resp;
});
}
}
else
{
return base.SendAsync(request, cancellationToken);
}
}
}

Wcf (400) Bad Request when object properties are incorrect

I have a Wcf (ajax enabled) service, that accepts a object for the method call. My Wcf method looks like this;
[OperationContract]
[XmlSerializerFormat]
[WebInvoke(Method = "POST", UriTemplate = "/XML/GetTypes", BodyStyle = WebMessageBodyStyle.Bare, ResponseFormat = WebMessageFormat.Xml)]
XElement XMLGetTypes(TypeRequest TypeRequest)
{
return Utilities.Convert.ObjectToXElement(TypeRequest.Execute(TypeRequest));
}
The 'TypeRequest' object is as follows;
public class
{
public int Id {get;set;}
public string Name {get; set;}
}
The problem I'm having is that if I call the method with invalid data like in the request object like;
<TypeRequest>
<Id>abc</Id>
<Name>Joe King</Name>
</TypeRequest>
As you can see the Id should be a integer, however it is being passed as a string. This causes a Bad Request 400 response from the Wcf service.
Ideally I'd like to handle the error, and return a suitable response. For example I'd like to return either a JSON or XML response containing the error information.
Is this possible ?
IIS allows you to create error handlers overall in the pipeline. How you handle the code and what you return is up to you, though I would be careful, as this handles all the errors for an IIS application.
public class ErrorHttpHandler : IHttpHandler
{
public bool IsReusable
{
get { return true; }
}
public void ProcessRequest(HttpContext context)
{
if (context.Request.QueryString.Count == 0)
return;
string strStatusCode = context.Request.QueryString[0].Split(';').FirstOrDefault() ?? "500";
int statusCode = 500;
int.TryParse(strStatusCode, out statusCode);
string message = "Unhandled server error.";
switch (statusCode)
{
case 400:
message = "Bad request.";
break;
case 404:
message = "Item not found.";
break;
}
context.Response.StatusCode = statusCode;
context.Response.Write(string.Format("<Error><Message>{0}</Message></Error>", message));
}
}
and in your web.config, add this code for the handler to be called:
<system.webServer>
<httpErrors errorMode="Custom">
<clear/>
<error statusCode="404" path="/application/ErrorHandler" responseMode="ExecuteURL"/>
<error statusCode="400" path="/application/ErrorHandler" responseMode="ExecuteURL"/>
</httpErrors>
<handlers>
<add name="ErrorHandler" path="ErrorHandler" verb="*" type="Your Application.ErrorHttpHandler, FrameworkAssembly"/>
</handlers>
</system.webServer>
All of this was culled from this site.
You can check your parameter using parameter inspector, which will allow you to throw fault exception with the message you need. Also you can provide your client (if it's .net client) with you Parameter inspector attribute. As a result the message will not be send till it pass validation, which can save you traffic. Here is a link:
WCF Parameter Validation with Interceptor
And in case your client send you a wrong message. You have to use message inspector:
MSDN, Logging all WCF messages
Here is even better example:
http://msdn.microsoft.com/en-us/library/ff647820.aspx
You have to put more attention for methods AfterReceiveRequest. Please notice that it's required to create a buffered copy of the message, then work with one copy of a message, and return another one.
it is possible to do this but you may need to do it a little differently, the easiest way may be to alter your incoming Type request as follows
public IncomingClass
{
public string Id {get;set;}
public string Name {get; set;}
}
this will mean that the incoming data is valid, and you won't get the error 400,
you can then validate this using
int myId = incomingClass.Id as int;
if (myId == null)
{
//put suitable error handling here
}
or perhaps better still stick the whole function in a try catch loop, and handle the error that way. I think you will find the error is happening before you are entering your function. your WCF Service is expecting xml that can be translated to the type of Object "TypeRequest" but your xml is invalid.
Hope that helps
I think you would need some method interceptors and write the expected logic in there.
Here's a good resource on that http://msdn.microsoft.com/en-us/magazine/cc163302.aspx

HttpHandler for prefix in url

I am trying to convert http call in aspx to https
Back Ground : i have a Aspx page that is in https site.on that page i have reference to script of google
Aspx page reference :
<script src="http://ajax.googleapis.com/ajax/libs/jquery/1.3/jquery.min.js" type="text/javascript"></script>
i have created a HttpHandler for Prefix Http
IHttpHandler Interface implementation :
public class HttpToHttpsHandler : IHttpHandler
{
public bool IsReusable
{
get { return false; }
}
public void ProcessRequest(HttpContext context)
{
try
{
context.Response.ContentType = "text/plain";
if (context.Request.RawUrl.Contains("http:"))
{
string newUrl = context.Request.RawUrl.Replace("http", "https");
context.Server.Transfer(newUrl);
}
}
catch (Exception)
{
throw;
}
}
}
Web.Config file registration code :
<httpHandlers>
<add verb="*" path="http:*" type="HttpToHttpsHandler , App_Code"/>
</httpHandlers>
But i am not getting the control in Http handler class.what could be the possible error.
I am wondering if your assembly is called "App_Code". In your type declaration you must enter the assembly name, not the folder name of the C# file.
The path attribute as far as I know it is relative position and it only can take one of two values: the name/file-name or the extension/file-extension to map. Like
<add verb="*" path="*.SampleFileExtension"
type="Example1 " />
Or
<add verb="*" path="demo.*"
type="Example1 " />
I tried a combination of both these and it also worked, which says that anything that starts with test and for any extension will be handler by handler:
<add verb="*" path="test*.*"
name="HelloWorldHandler"
type="demo.HelloWorldHandler,App_Code" />
But please notice that it is a relative path, so it means it does not include the http or https values from the URL and therefore a Handler cannot be used to validate URLs.
You need to define your assembly name which contains the HttpToHandler class.
The handler is defined as the class HttpToHttpsHandler in the your assembly which if is in the same project then it will be your application name.
Go through this article
<httpHandlers>
<add verb="*" path="*.aspx"
type="HttpToHttpsHandler , AssemblyName" />
</httpHandlers>
</system.web>
if (!Request.IsLocal && !Request.IsSecureConnection)
{
string redirectUrl = Request.Url.ToString().Replace("http:", "https:");
Response.Redirect(redirectUrl);
}
HttpRequest.IsSecureConnection Property determines whether the HTTP connection uses secure sockets ( HTTPS) or not .-MSDN

Uploadify ashx file Context.Session gets null

I have a file upload in my site which is done using uploadify it uses a ashx page to upload file to database.It works fine in IE but in Mozilla the context.Session is getting null.I have also used IReadOnlySessionState to read session.
how can i get session in Mozilla like IE.
here is the ashx code i have done
public class Upload : IHttpHandler, IReadOnlySessionState
{
HttpContext context;
public void ProcessRequest(HttpContext context)
{
string UserID = context.Request["UserID"];
context.Response.ContentType = "text/plain";
context.Response.Expires = -1;
XmlDocument xDoc = new XmlDocument();
HttpPostedFile postedFile = context.Request.Files["Filedata"];
try
{
if (context.Session["User"] == null || context.Session["User"].ToString() == "")
{
context.Response.Write("SessionExpired");
context.Response.StatusCode = 200;
}
else
{
// does the uploading to database
}
}
}
}
In IE Context.Session["User"] always have the value but in Mozilla it is always null
You need to add sessionId to uploadify post params and restore ASP.NET_SessionId cookie on the server side on global.asax at OnBeginRequest. It is actually bug with flash and cookies.
I have created module for session and auth cookie restore, to get work flash and asp.net session, so i think it will be useful for your:
public class SwfUploadSupportModule : IHttpModule
{
public void Dispose()
{
// clean-up code here.
}
public void Init(HttpApplication application)
{
application.BeginRequest += new EventHandler(OnBeginRequest);
}
private void OnBeginRequest(object sender, EventArgs e)
{
var httpApplication = (HttpApplication)sender;
/* we guess at this point session is not already retrieved by application so we recreate cookie with the session id... */
try
{
string session_param_name = "ASPSESSID";
string session_cookie_name = "ASP.NET_SessionId";
if (httpApplication.Request.Form[session_param_name] != null)
{
UpdateCookie(httpApplication, session_cookie_name, httpApplication.Request.Form[session_param_name]);
}
else if (httpApplication.Request.QueryString[session_param_name] != null)
{
UpdateCookie(httpApplication, session_cookie_name, httpApplication.Request.QueryString[session_param_name]);
}
}
catch
{
}
try
{
string auth_param_name = "AUTHID";
string auth_cookie_name = FormsAuthentication.FormsCookieName;
if (httpApplication.Request.Form[auth_param_name] != null)
{
UpdateCookie(httpApplication, auth_cookie_name, httpApplication.Request.Form[auth_param_name]);
}
else if (httpApplication.Request.QueryString[auth_param_name] != null)
{
UpdateCookie(httpApplication, auth_cookie_name, httpApplication.Request.QueryString[auth_param_name]);
}
}
catch
{
}
}
private void UpdateCookie(HttpApplication application, string cookie_name, string cookie_value)
{
var httpApplication = (HttpApplication)application;
HttpCookie cookie = httpApplication.Request.Cookies.Get(cookie_name);
if (null == cookie)
{
cookie = new HttpCookie(cookie_name);
}
cookie.Value = cookie_value;
httpApplication.Request.Cookies.Set(cookie);
}
}
Also than you need register above module at web.config:
<system.webServer>
<modules runAllManagedModulesForAllRequests="true">
<add name="SwfUploadSupportModule" type="namespace.SwfUploadSupportModule, application name" />
</modules>
</system.webServer>
Context.Session is null.. because connection to HttpHandler has another Context.Session
(debug and try: Context.Session.SessionId in where is the fileInput is different from Context.Session.SessionId in Upload.ashx)!
I suggest a workaround: pass a reference to the elements you need in the second session ( in my sample i pass the original SessionId using sessionId variable)
....
var sessionId = "<%=Context.Session.SessionID%>";
var theString = "other param,if needed";
$(document).ready(function () {
$('#fileInput').uploadify({
'uploader': '<%=ResolveUrl("~/uploadify/uploadify.swf")%>',
'script': '<%=ResolveUrl("~/Upload.ashx")%>',
'scriptData': { 'sessionId': sessionId, 'foo': theString },
'cancelImg': '<%=ResolveUrl("~/uploadify/cancel.png")%>',
....
and use this items in .ashx file.
public void ProcessRequest(HttpContext context)
{
try
{
HttpPostedFile file = context.Request.Files["Filedata"];
string sessionId = context.Request["sessionId"].ToString();
....
If you need to share complex elements use Context.Application instead of Context.Session, using original SessionID: Context.Application["SharedElement"+SessionID]
It's likely to be something failing to be set by the server or sent back on the client.
Step back to a lower level - use a network diagnostic tool such as Fiddler or Wireshark to examine the traffic being sent to/from your server and compare the differences between IE and Firefox.
Look at the headers to ensure that cookies and form values are being sent back to the server as expected.
I have created a function to check session have expired and then pass that as a parameter in script-data of uploadify and in ashx file i check that parameter to see whether session exists or not.if it returns session have expired then upload will not take place.It worked for me. Did not find any issues using that. hope that solve my issue
I had a similar problem with an .ashx file. The solution was that the handler has to implement IReadOnlySessionState (for read-only access) or IRequiresSessionState (for read-write access). eg:
public class SwfUploadSupportModule : IHttpHandler, IRequiresSessionState { ... }
These Interfaces do not need any additional code but act as markers for the framework.
Hope that this helps.
Jonathan

Categories