Web API and HTTP Module - c#

We have an HTTP Module that decodes all encoded requests.
It works great with all WCF requests, but NOT in Web Api requests- in Web Api the request (both POST and GET) gets to the service still encoded
I see that it Hits the HTTP Module but,again,still gets to the service encoded.
How can i fix it? or what am i doing wrong?
i know that its better to work with Message Handlers in Web Api, but HTTP Modules suppose to work too- no?
HTTP Module:
public void Init(HttpApplication context)
{
context.BeginRequest += new EventHandler(context_BeginRequest);
context.EndRequest += context_PreSendRequestContent;
}
void context_PreSendRequestContent(object sender, EventArgs e)
{
string encodedQuerystring = HttpContext.Current.Request.QueryString.ToString();
if (!string.IsNullOrEmpty(encodedQuerystring))
{
System.Collections.Specialized.NameValueCollection col = new System.Collections.Specialized.NameValueCollection();
col.Add("q", encodedQuerystring);
WebFunction.CreateQuerystring(HttpContext.Current, col);
}
}
void context_BeginRequest(object sender, EventArgs e)
{
string encodedQueryString = String.Empty;
if (HttpContext.Current.Request.QueryString.Count > 0 && HttpContext.Current.Request.QueryString["q"] != null)
{
object _p = HttpContext.Current.Request.QueryString;
encodedQueryString = HttpContext.Current.Server.UrlDecode(HttpContext.Current.Request.QueryString["q"].ToString());
string originalQueryString = HttpContext.Current.Server.UrlDecode(WebFunction.Base64Decode(encodedQueryString));
if (!string.IsNullOrEmpty(originalQueryString))
{
WebFunction.CreateQuerystring(HttpContext.Current, WebFunction.ConvertQueryToCollection(originalQueryString));
}
}
}
WebFunction:
public static void CreateQuerystring(HttpContext context, System.Collections.Specialized.NameValueCollection nameValueCollection)
{
// reflect to readonly property
PropertyInfo isreadonly = typeof(System.Collections.Specialized.NameValueCollection).GetProperty("IsReadOnly", BindingFlags.Instance | BindingFlags.NonPublic);
// make collection editable
isreadonly.SetValue(context.Request.QueryString, false, null);
context.Request.QueryString.Clear();
context.Request.QueryString.Add(nameValueCollection);
// make collection readonly again
isreadonly.SetValue(context.Request.QueryString, true, null);
}
Web Api:
public class NamesController : ApiController
{
[HttpGet]
[ActionName("GET_NAMES")]
public Drugs_ResponseData Get(string q)
{
//need to add the decode function to get it to work
string[] arrAmpersant = Commonnn.DecodeFrom64(q).Split('&');
Names_obj = new Names();
return _obj.GetResult(Convert.ToInt32(Commonnn.GetValFromEqual(arrAmpersant[0])));
}
}

It seems that the Web API doesn't use the QueryString collection in the request, but it parses the URL itself.
See the GetQueryNameValuePairs method in this file - they take the Uri and parse its Query property.
So you have two options to do that:
The dirty one is to change the Uri of the request in your the HTTP module. I don't know whether it's possible, but some reflection could do the trick.
The nicer way would be to use the Web API message handler.

May I suggest you use Context.Items and let the QueryString have the encoded version.
It's a not very well known built in key/value dictionary which last throughout a request where you easily store any object and then share it between module, handlers, etc.
Using this would very like give you a better performance than unlocking the QueryString object, but more importantly, you process the value in one place and reuse it in many, and when needed, you just add a second value, the complete QueryString collection or any other value you want to share across a request.
void context_BeginRequest(object sender, EventArgs e)
{
string encodedQueryString = String.Empty;
if (HttpContext.Current.Request.QueryString.Count > 0 && HttpContext.Current.Request.QueryString["q"] != null)
{
string encodedQueryString = HttpContext.Current.Server.UrlDecode(HttpContext.Current.Request.QueryString["q"].ToString());
HttpContext.Current.Items("qs_d") = HttpContext.Current.Server.UrlDecode(WebFunction.Base64Decode(encodedQueryString));
}
}
Web Api:
public class NamesController : ApiController
{
[HttpGet]
[ActionName("GET_NAMES")]
public Drugs_ResponseData Get(string q)
{
string[] arrAmpersant = Commonnn.DecodeFrom64(HttpContext.Current.Items("qs_d").ToString()).Split('&');
Names_obj = new Names();
return _obj.GetResult(Convert.ToInt32(Commonnn.GetValFromEqual(arrAmpersant[0])));
}
}
Side note: I see you call HttpContext.Current.Server.UrlDecode twice. I don't think you need that unless your Base64Decode method encode the value again.

You can handle like this
protected void Application_BeginRequest(object sender, EventArgs e)
{
var app = (HttpApplication)sender;
string path = app.Context.Request.Url.PathAndQuery;
int pos = path.IndexOf("?");
if (pos > -1)
{
string[] array = path.Split('?');
app.Context.RewritePath(array[0]+"?"+ HttpContext.Current.Server.UrlDecode(array[1]));
}
}

Adding on to #Tomáš Herceg 's answer, I would implement a Web Api message handler rather than modifying your HttpModule to accommodate Web Api.
public class DecodeQueryStringMessageHandler : DelegatingHandler
{
protected override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request,
CancellationToken cancellationToken)
{
if (request.Method == HttpMethod.Get)
{
var originalQueryString = request.RequestUri.Query;
if (!string.IsNullOrEmpty(originalQueryString))
{
var ub = new UriBuilder(request.RequestUri) { Query = HttpUtility.UrlDecode(originalQueryString) };
request.RequestUri = ub.Uri;
}
}
return base.SendAsync(request, cancellationToken);
}
}

It is possible, but you will need reflection, what means that exist a risk here. Please, let me suggest you what I consider to be a more clean solution after the solution.
Solution
if (!string.IsNullOrEmpty(originalQueryString))
{
var request = HttpContext.Current.Request;
request.GetType().InvokeMember("QueryStringText", BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.SetProperty, null, request, new[] { "q=" + originalQueryString });
//WebFunction.CreateQuerystring(HttpContext.Current, WebFunction.ConvertQueryToCollection(originalQueryString));
}
This will update the following properties of the Request:
Request.Param
Request.QueryString
Request.ServerVariables
Request.Url
but will not update the:
Request.RawUrl
Cleaner Solution
IIS URL Rewrite Module
http://www.iis.net/learn/extensions/url-rewrite-module/developing-a-custom-rewrite-provider-for-url-rewrite-module

Related

C# Method or Object cannot be called second time

I am a total noob in C# and tried to add an automated mail services to my backend API for an Angular FrontEnd.
It works properly as intended for one time, but cannot be used a second time. I guess I am violating some object rules, maybe someone is able to point out my mistake.
This is an excerpt of my file UserAuthController.cs which includes the register function. When registration on my website is successful it shall also call the API Service from my automated mail system.
I didn't know how to include the function properly so I've added it with a new namespace.
namespace Gogo_Api.Controllers
{
[RoutePrefix("UserAuth")]
[EnableCors(origins: "*", headers: "*", methods: "*")]
public class UserAuthController : ApiController
{
private readonly IUserAuthManager _userAuthManager;
public UserAuthController()
{
}
public UserAuthController(IUserAuthManager userAuthManager)
{
this._userAuthManager = userAuthManager;
}
[HttpPost]
[Route("Register")]
public HttpResponseMessage Register(UserDetails user)
{
var response = new RegisterResponse();
response.code = _userAuthManager.Register(user);
if (response.code == 1)
{
response.message = "Registration successful ";
//Including API Service here...
Sendinblue.Program RegisterMail = new Sendinblue.Program();
RegisterMail.Main(user.email, user.displayName, user.country);
RegisterMail = null;
}
else if (response.code == 2)
{
response.message = "User already registered ";
}
else if (response.code == 0)
{
response.message = "Error occured";
}
return Request.CreateResponse(HttpStatusCode.OK, response);
}
}
}
namespace Sendinblue
{
class Program
{
public void Main(string userMail, string userName, string userCountry)
{
Configuration.Default.ApiKey.Add("api-key", "MYKEY");
var apiInstance = new ContactsApi();
string email = userMail;
JObject attributes = new JObject();
attributes.Add("USERNAME", userName);
attributes.Add("COUNTRY", userCountry);
List<long?> listIds = new List<long?>();
listIds.Add(5);
try
{
var createContact = new CreateContact(email, attributes, emailBlacklisted, smsBlacklisted, listIds, updateEnabled, smtpBlacklistSender);
CreateUpdateContactModel result = apiInstance.CreateContact(createContact);
}
catch (Exception e)
{
Debug.WriteLine(e.Message);
Console.WriteLine(e.Message);
}
}
}
}
I have added 'RegisterMail = null;' because I thought I need to delete my object first before using it again, but still it works only for the first time.
How would I be able to call my function multiple times?
Thanks
Thanks #Richard Barker, your comment helped me fix it.
I have moved everything out of the Controller and had to move
Configuration.Default.ApiKey.Add...
apiInstance = new ContactsApi();
to a one-time call in the Initializer, so the API Call and ContactsApi is only created once, instead of everytime.

WebAPI - Resolve controller and action manually from the request

I wanted to make my WebAPI application change the used SessionStateBehavior based on action attributes like that:
[HttpPost]
[Route("api/test")]
[SetSessionStateBehavior(SessionStateBehavior.Required)] // <--- This modifies the behavior
public async Task<int> Test(){}
It seems, however, that the only place I can change the session behavior is inside my HttpApplication's Application_PostAuthorizeRequest (or in similar places, early in the request lifetime), otherwise I get this error:
'HttpContext.SetSessionStateBehavior' can only be invoked before 'HttpApplication.AcquireRequestState' event is raised.
So, at that point no controller or action resolution is done, so I don't know what action will be called in order to check its attributes.
So, I am thinking of resolving the action manually.
I started with these lines of code to resolve the controller first:
var httpCtx = HttpContext.Current;
var ctrlSel = GlobalConfiguration.Configuration.DependencyResolver.GetService(typeof(IHttpControllerSelector)) as IHttpControllerSelector;
var actionSel = GlobalConfiguration.Configuration.DependencyResolver.GetService(typeof(IHttpActionSelector)) as IHttpActionSelector;
HttpControllerDescriptor controllerDescriptor = ctrlSel.SelectController(httpCtx.Request);
But in the last line I can't get the proper HttpRequestMessage from the request.
Any idea ho how get that?
This is not inside a controller, so I don't have it ready there.
Or, is there a better way to do this?
I am trying to see the disassembled code of the framework to copy portions of it, but I am quite lost at this point...
UPDATE:
This is the closest I got to resolving the action manually, but it doesn't work:
I have registered those two services:
container.RegisterType<IHttpControllerSelector, DefaultHttpControllerSelector>();
container.RegisterType<IHttpActionSelector, ApiControllerActionSelector>();
...and try to get the required session behavior like that:
private SessionStateBehavior GetDesiredSessionBehavior(HttpContext httpCtx)
{
var config = GlobalConfiguration.Configuration;
var diResolver = config.Services;
var ctrlSel = diResolver.GetService(typeof(IHttpControllerSelector)) as IHttpControllerSelector;
var actionSel = diResolver.GetService(typeof(IHttpActionSelector)) as IHttpActionSelector;
if (ctrlSel is null || actionSel is null)
{
return DefaultSessionBehavior;
}
var method = new HttpMethod(httpCtx.Request.HttpMethod);
var requestMsg = new HttpRequestMessage(method, httpCtx.Request.Url);
requestMsg.Properties.Add(HttpPropertyKeys.RequestContextKey, httpCtx.Request.RequestContext);
requestMsg.Properties.Add(HttpPropertyKeys.HttpConfigurationKey, config);
httpCtx.Request.Headers.Cast<string>().ForEach(x => requestMsg.Headers.Add(x, httpCtx.Request.Headers[x]));
var httpRouteData = httpCtx.Request.RequestContext.RouteData;
var routeData = config.Routes.GetRouteData(requestMsg);
requestMsg.Properties.Add(HttpPropertyKeys.HttpRouteDataKey, routeData);
requestMsg.SetRequestContext(new HttpRequestContext(){RouteData = routeData });
requestMsg.SetConfiguration(config);
var route = config.Routes["DefaultApi"];
requestMsg.SetRouteData(routeData ?? route.GetRouteData(config.VirtualPathRoot, requestMsg));
var routeHandler = httpRouteData.RouteHandler ?? new WebApiConfig.SessionStateRouteHandler();
var httpHandler = routeHandler.GetHttpHandler(httpCtx.Request.RequestContext);
if (httpHandler is IHttpAsyncHandler httpAsyncHandler)
{
httpAsyncHandler.BeginProcessRequest(httpCtx, ar => httpAsyncHandler.EndProcessRequest(ar), null);
}
else
{
httpHandler.ProcessRequest(httpCtx);
}
var values = requestMsg.GetRouteData().Values; // Hm this is empty and makes the next call fail...
HttpControllerDescriptor controllerDescriptor = ctrlSel.SelectController(requestMsg);
IHttpController controller = controllerDescriptor?.CreateController(requestMsg);
if (controller == null)
{
return DefaultSessionBehavior;
}
var ctrlContext = CreateControllerContext(requestMsg, controllerDescriptor, controller);
var actionCtx = actionSel.SelectAction(ctrlContext);
var attr = actionCtx.GetCustomAttributes<ActionSessionStateAttribute>().FirstOrDefault();
return attr?.Behavior ?? DefaultSessionBehavior;
}
I have an alternative hack to make it work (send header values from the client to modify the session behavior), but it would be nice if the version above worked.
UPDATE:
Eventually, I went with setting the session behavior based on a client header value and validating the validity of sending that header based on the action attributes later-on in the request lifetime. If someone can solve the action resolution code I was fighting with above, feel free to post the answer here.
I don't know if this is going to be helpful for you, but I was just following a Pluralsight course (https://app.pluralsight.com/player?course=implementing-restful-aspdotnet-web-api) and in the Versioning chapter the author shows how to implement a controller selector where he does have access to the request.
The controller selector looks like:
public class CountingKsControllerSelector : DefaultHttpControllerSelector
{
private HttpConfiguration _config;
public CountingKsControllerSelector(HttpConfiguration config)
: base(config)
{
_config = config;
}
public override HttpControllerDescriptor SelectController(HttpRequestMessage request)
{
var controllers = GetControllerMapping();
var routeData = request.GetRouteData();
var controllerName = (string)routeData.Values["controller"];
HttpControllerDescriptor descriptor;
if (controllers.TryGetValue(controllerName, out descriptor))
{
[...]
return descriptor;
}
return null;
}
}
And it's registered in WebApiConfig with:
config.Services.Replace(typeof(IHttpControllerSelector),
new CountingKsControllerSelector(config));

How can I get my Json object from URL, Windows Phone 8.1 (C#)

Suppose I have this data:
{
"A": "Z",
"B": {
"C": "Y",
"D": "X"
}
}
stored in this link.
now I want this data from my windows phone app during a button click.
private void login(object sender, RoutedEventArgs e)
{
//I want to read the json data here
}
Previously I have tried DownloadStringAsync method of WebClient. But this class is no longer available in System.Net. I would like to know the place and documents where I can learn how to get and post data using url.
Thanks.
using (var client = new WebClient())
{
var uri = "https://api.myjson.com/bins/2hxei";
var result = System.Text.Encoding.Default.GetString(client.DownloadData(uri));
// result : {"A":"Z","B":{"C":"Y","D":"X"}}
}
btw. WebClient is definitely still available in .NET
If you really do not want to use Web Client (older) and want to use the newer Http Client instead, use the following code.
public static string GetJsonData()
{
var promise = GetWebStringAsync("https://api.myjson.com/bins/2hxei");
// do something else if needed
var jsonData = promise.Result;
return jsonData; //{"A":"Z","B":{"C":"Y","D":"X"}}
}
public static async Task<string> GetWebStringAsync(string uri)
{
using (var client = new HttpClient())
{
var stringTask = await client.GetStringAsync(uri);
return stringTask;
}
}
Here, if you want, rather than doing var stringTask = await client.GetStringAsync(uri) you can also do var jsonString= client.GetStringAsync(uri).Result directly. But you'll lose the benefits of aynchronous method there, and the call will be synchronous. The calling thread will wait there until response is received from the web call.

Why do I always get the same IP when getting Request.UserHostAddress in WebAPI custom action filter?

I've created a Web API custom action filter to log incoming calls. I'm trying to get the caller's IP address and everything I've found says to use Request.UserHostAddress. The problem is that no matter where the call is coming from, the IP is the same.
Here's the code for my action filter:
public class LogActionFilter : ActionFilterAttribute
{
public override void OnActionExecuting(HttpActionContext actionContext)
{
var name = actionContext.ActionDescriptor.ActionName;
// Get the sender address
var caller = ((HttpContextWrapper)actionContext.Request.Properties["MS_HttpContext"]).Request.UserHostAddress;
// Log the call
SystemBL.InsertSiteLog("WebAPI:" + name, "From:" + caller);
}
}
I've also tried with:
var caller = ((HttpContextWrapper)actionContext.Request.Properties["MS_HttpContext"]).Request.ServerVariables["REMOTE_ADDR"].ToString();
but the result was the same. Any ideas?
Found the answer here: HttpContext.Current.Request.UserHostAddress is null.
Basically, I needed to sort out the forwarding. The final code is:
public override void OnActionExecuting(HttpActionContext actionContext)
{
var name = actionContext.ActionDescriptor.ActionName;
// Get the sender address
var myRequest = ((HttpContextWrapper)actionContext.Request.Properties["MS_HttpContext"]).Request;
var ip = myRequest.ServerVariables["HTTP_X_FORWARDED_FOR"];
if (!string.IsNullOrEmpty(ip))
{
string[] ipRange = ip.Split(',');
int le = ipRange.Length - 1;
string trueIP = ipRange[le];
}
else
{
ip = myRequest.ServerVariables["REMOTE_ADDR"];
}
// Log the call
SystemBL.InsertSiteLog("WebAPI:" + name, "From:" + ip);
}
Thanks everyone. I'll mark it as the answer in 2 days when it lets me.

Can gzip compression be selectively disabled in ASP.NET/IIS 7?

I am using a long-lived asynchronous HTTP connection to send progress updates to a client via AJAX. When compression is enabled, the updates are not received in discrete chunks (for obvious reasons). Disabling compression (by adding a <urlCompression> element to <system.webServier>) does solve the problem:
<urlCompression doStaticCompression="true" doDynamicCompression="false" />
However, this disables compression site-wide. I would like to preserve compression for every other controller and/or action except for this one. Is this possible? Or am I going to have to create a new site/area with its own web.config? Any suggestions welcome.
P.S. the code that does the writing to the HTTP response is:
var response = HttpContext.Response;
response.Write(s);
response.Flush();
#Aristos' answer will work for WebForms, but with his help, I've adapted a solution more inline with ASP.NET/MVC methodology.
Create a new filter to provide the gzipping functionality:
public class GzipFilter : ActionFilterAttribute
{
public override void OnActionExecuted(ActionExecutedContext filterContext)
{
base.OnActionExecuted(filterContext);
var context = filterContext.HttpContext;
if (filterContext.Exception == null &&
context.Response.Filter != null &&
!filterContext.ActionDescriptor.IsDefined(typeof(NoGzipAttribute), true))
{
string acceptEncoding = context.Request.Headers["Accept-Encoding"].ToLower();;
if (acceptEncoding.Contains("gzip"))
{
context.Response.Filter = new GZipStream(context.Response.Filter, CompressionMode.Compress);
context.Response.AppendHeader("Content-Encoding", "gzip");
}
else if (acceptEncoding.Contains("deflate"))
{
context.Response.Filter = new DeflateStream(context.Response.Filter, CompressionMode.Compress);
context.Response.AppendHeader("Content-Encoding", "deflate");
}
}
}
}
Create the NoGzip attribute:
public class NoGzipAttribute : Attribute {
}
Prevent IIS7 from gzipping using web.config:
<system.webServer>
...
<urlCompression doStaticCompression="true" doDynamicCompression="false" />
</system.webServer>
Register your global filter in Global.asax.cs:
protected void Application_Start()
{
...
GlobalFilters.Filters.Add(new GzipFilter());
}
Finally, consume the NoGzip attribute:
public class MyController : AsyncController
{
[NoGzip]
[NoAsyncTimeout]
public void GetProgress(int id)
{
AsyncManager.OutstandingOperations.Increment();
...
}
public ActionResult GetProgressCompleted()
{
...
}
}
P.S. Once again, many thanks to #Aristos, for his helpful idea and solution.
I found a much easier way to do this. Instead of selectively doing your own compression, you can selectively disable the default IIS compression (assuming its enabled in your web.config).
Simply remove the accept-encoding encoding header on the request and IIS wont compress the page.
(global.asax.cs:)
protected void Application_BeginRequest(object sender, EventArgs e)
{
try
{
HttpContext.Current.Request.Headers["Accept-Encoding"] = "";
}
catch(Exception){}
}
What about you set the gzip compression by your self, selectivle when you wish for ? On the Application_BeginRequest check when you like to make and when you dont make compression. Here is a sample code.
protected void Application_BeginRequest(Object sender, EventArgs e)
{
string cTheFile = HttpContext.Current.Request.Path;
string sExtentionOfThisFile = System.IO.Path.GetExtension(cTheFile);
if (sExtentionOfThisFile.Equals(".aspx", StringComparison.InvariantCultureIgnoreCase))
{
string acceptEncoding = MyCurrentContent.Request.Headers["Accept-Encoding"].ToLower();;
if (acceptEncoding.Contains("deflate") || acceptEncoding == "*")
{
// defalte
HttpContext.Current.Response.Filter = new DeflateStream(prevUncompressedStream,
CompressionMode.Compress);
HttpContext.Current.Response.AppendHeader("Content-Encoding", "deflate");
} else if (acceptEncoding.Contains("gzip"))
{
// gzip
HttpContext.Current.Response.Filter = new GZipStream(prevUncompressedStream,
CompressionMode.Compress);
HttpContext.Current.Response.AppendHeader("Content-Encoding", "gzip");
}
}
}

Categories

Resources