WebRequest.RegisterPrefix for http:// returns true, doesn't work - networking

I'm trying to use WebRequest.RegisterPrefix to register a decorator IWebRequestCreate implementation with the intention being to add "debug" scenarios (like emulating different connectivity scenarios).
I'm using the Mango beta 2 SDK and the RegisterPrefix method always returns true when used with "http://" as a prefix (or "http" for that matter), but the registered IWebRequestCreate instance is not being used.
I can see from the documentation that it should return false for duplicates, but it doesn't seem to be functioning as documented.
Is there any other way of achieving what I'm after in a way that is transparent to consumers?

I'm using WebRequest.RegisterPrefix for unit testing, registering an IWebRequestCreate implementation for a prefix of test://, and this does work.
I found that after registering an IWebRequestCreate for http://, calling WebRequest.Create with an http:// uri would return a request created from the registered IWebRequestCreate, but calling WebRequest.CreateHttp would still return an HttpWebRequest.
The following code should verify this, and I'm using the Mango Beta 2 SDK (6-29-11):
public partial class MainPage : PhoneApplicationPage
{
public class FakeRequest : WebRequest
{
private Uri _uri;
public FakeRequest(Uri uri)
{
_uri = uri;
}
public override Uri RequestUri { get { return _uri; } }
}
public class FakeRequestFactory : IWebRequestCreate
{
public WebRequest Create(Uri uri)
{
return new FakeRequest(uri);
}
}
// Constructor
public MainPage()
{
InitializeComponent();
// returns System.Net.Browser.ClientHttpWebRequest
var request1 = WebRequest.Create("http://www.foo.com");
// returns System.Net.Browser.ClientHttpWebRequest
var request2 = WebRequest.CreateHttp("http://www.foo.com");
// returns true
bool result1 = WebRequest.RegisterPrefix("http://", new FakeRequestFactory());
// returns FakeRequest
var request3 = WebRequest.Create("http://www.foo.com");
// returns System.Net.Browser.ClientHttpWebRequest
var request4 = WebRequest.CreateHttp("http://www.foo.com");
// returns false
bool result2 = WebRequest.RegisterPrefix("http://", new FakeRequestFactory());
// returns false, as per the note in the documention
bool result3 = HttpWebRequest.RegisterPrefix("http://", new FakeRequestFactory());
}
}

Hey I know this question is 2 years old but I came across the same problem. I think you'll find that WebRequest.RegisterPrefix() does return false if you try to register http: (notice the single colon, no forward slashes). If I ever find a workaround, I'll try to remember to update this post.
EDIT
In my particular case I wanted to throw out System.Net.FtpWebRequest and roll my own FTP client implementation (because the framework's implementation sucks).
In order to do that, I used reflection (and a bunch of late binding tricks) to get the arraylist of registered prefix and remove the ones that are linked to the internal System.Net.FtpWebRequestCreator class.
I'm not sure if all of these APIs are available for windows phone, but here's what I did:
Type webRequest = typeof(System.Net.WebRequest);
Assembly system = Assembly.GetAssembly(webRequest);
Type ftpWebRequestCreator = system.GetType("System.Net.FtpWebRequestCreator");
ArrayList prefixList = (ArrayList)webRequest.GetProperty("PrefixList", BindingFlags.Static | BindingFlags.NonPublic).GetValue(null, null);
IEnumerator enumerator = prefixList.GetEnumerator();
while (enumerator != null && enumerator.MoveNext()) {
if (object.ReferenceEquals(enumerator.Current.Creator.GetType(), ftpWebRequestCreator)) {
prefixList.Remove(enumerator.Current);
if (System.Net.WebRequest.RegisterPrefix(enumerator.Current.Prefix, new CustomWebRequestCreator())) {
enumerator = null;
} else {
enumerator = prefixList.GetEnumerator();
}
}
}
// Now I can use Create() on the base class
System.Net.WebRequest myCustomWebRequest = System.Net.WebRequest.Create("ftp://example.com/public");
This works by finding all prefixes that are registered with FtpWebRequestCreator and replacing them with my own creator. It should be fairly straightforward to adapt this for http(s).

The phone only has a client stack so RegisterPrefix has no effect on the phone.

Related

How can I use a default value/model on WebAPI EmptyBody?

I have dotnet WebAPI and I'm trying to get a specific behaviour but am constantly getting 415 responses.
I have reproduced this by starting a new webapi project using dotnet new webapi on the command line. From there, I added two things: a new controller, and a model class. In my real project the model class is obviously a bit more complex, with inheritance and methods etc...
Here they are:
[HttpGet("/data")]
public async Task<IActionResult> GetModel(BodyParams input)
{
var response = new { Message = "Hello", value = input.valueOne };
return Ok(response);
}
public class BodyParams {
public bool valueOne { get; set; } = true;
}
My goal is that the user can call https://localhost:7222/data with no headers or body needed at all, and will get the response - BodyParams will be used with the default value of true. Currently, from postman, or from the browser, I get a 415 response.
I've worked through several suggestions on stack and git but nothing seems to be working for me. Specifically, I have tried:
Adding [FromBody(EmptyBodyBehavior = EmptyBodyBehavior.Allow)] into the controller, but this makes no difference unless I provide an empty {} json object in the body. This is not what I want.
Making BodyParams nullable - again, no change.
Adding .AddControllers(opt => opt.AllowEmptyInputInBodyModelBinding = true)... again, no change.
I Implemented the solution suggested here using the attribute modification in the comment by #HappyGoLucky. Again, this did not give the desired outcome, but it did change the response to : 400 - "The input does not contain any JSON tokens. Expected the input to start with a valid JSON token, when isFinalBlock is true."
I tried modifying the solution in (4) to manually set context.HttpContext.Request.Body to an empty json object... but I can't figure out the syntax for this because it need to be a byte array and at that point I feel like I am way over complicating this.
How can I get the controller to use BodyParams with default values in the case that the user provides no body and no headers at all?
You can achieve that using a Minimal API.
app.MapGet("/data",
async (HttpRequest httpRequest) =>
{
var value = true;
if (Equals(httpRequest.GetTypedHeaders().ContentType, MediaTypeHeaderValue.Parse("application/json")))
{
var bodyParams = await httpRequest.ReadFromJsonAsync<BodyParams>();
if (bodyParams is not null) value = bodyParams.ValueOne;
}
var response = new {Message = "Hello", value};
return Results.Ok(response);
});
So, as there doesn't seem to be a more straightforward answer, I have currently gone with the approach number 5) from the OP, and just tweaking the code from there very slightly.
All this does is act as an action which checks the if the user has passed in any body json. If not, then it adds in an empty anonymous type. The behaviour then is to use the default True value from the BodyParams class.
The full code for the action class is:
internal class AllowMissingContentTypeForEmptyBodyConvention : Attribute, IActionModelConvention
{
public void Apply(ActionModel action)
{
action.Filters.Add(new AllowMissingContentTypeForEmptyBodyFilter());
}
private class AllowMissingContentTypeForEmptyBodyFilter : IResourceFilter
{
public void OnResourceExecuting(ResourceExecutingContext context)
{
if (!context.HttpContext.Request.HasJsonContentType()
&& (context.HttpContext.Request.ContentLength == default
|| context.HttpContext.Request.ContentLength == 0))
{
context.HttpContext.Request.ContentType = "application/json";
var str = new { };
//convert string to jsontype
var json = JsonConvert.SerializeObject(str);
//modified stream
var requestData = Encoding.UTF8.GetBytes(json);
context.HttpContext.Request.Body = new MemoryStream(requestData);
}
}
public void OnResourceExecuted(ResourceExecutedContext context)
{
// Do nothing
}
}
}
Then you can add this to any of your controllers using [AllowMissingContentTypeForEmptyBodyConvention]

Changing Request Path in .Net Core 3.1

Prior to 3.0, I could change the path of a request (without any form of browser redirection) by just accessing the HttpRequest property of the HttpContext and then changed the value of the Path.
As an example, to display a page for a user who needed to change his/her password (irrespective of the page the user intended to visit), I extended the HttpContext
public static void ChangeDefaultPassword(this HttpContext context)
=> context.Request.Path = "/Account/ChangePassword";
This piece of code takes the user to the action method ChangePassword in the AccountController without executing the action method the user intends to visit.
Then enters dotnet core 3.1.
In 3.1, the extension method changes the path. However, it never executes the action method. It ignores the updated path.
I am aware this is due to the changes in the routing.The endpoint can now be accessed with the extension method HttpContext.GetEndpoint(). There is also an extension method HttpContext.SetEndpoint which seems to be the right way to set a new endpoint. However, there is no sample of how to accomplish this.
The Question
How do I change the request path, without executing the original path?
What I Have Tried
I tried changing the path. It seems routing in dotnet core 3.1 ignores the value of the HttpRequest path value.
I tried redirecting with context.Response.Redirect("/Account/ChangePassword");. This worked but it first executed the original action method requested by the user. This behavior defeated the purpose.
I tried using the extension method HttpContext.SetEndpoint, but there was no example available to work with.
The way I worked around this issue is to use EndpointDataSource directly, which is a singleton service that is available from DI as long as you have the routing services registered. It works as long as you can provide the controller name and the action name, which you can specify at compile time. This negates the need to use IActionDescriptorCollectionProvider or build the endpoint object or request delegate by yourself (which is pretty complicated...):
public static void RerouteToActionMethod(this HttpContext context, EndpointDataSource endpointDataSource, string controllerName, string actionName)
{
var endpoint = endpointDataSource.Endpoints.FirstOrDefault(e =>
{
var descriptor = e.Metadata.GetMetadata<ControllerActionDescriptor>();
// you can add more constraints if you wish, e.g. based on HTTP method, etc
return descriptor != null
&& actionName.Equals(descriptor.ActionName, StringComparison.OrdinalIgnoreCase)
&& controllerName.Equals(descriptor.ControllerName, StringComparison.OrdinalIgnoreCase);
});
if (endpoint == null)
{
throw new Exception("No valid endpoint found.");
}
context.SetEndpoint(endpoint);
}
I was able to find a working solution. My solution works by manually setting a new endpoint with the SetEndpoint extension method.
Here is an extension method I created to resolve this issue.
private static void RedirectToPath(this HttpContext context, string controllerName, string actionName )
{
// Get the old endpoint to extract the RequestDelegate
var currentEndpoint = context.GetEndpoint();
// Get access to the action descriptor collection
var actionDescriptorsProvider =
context.RequestServices.GetRequiredService<IActionDescriptorCollectionProvider>();
// Get the controller aqction with the action name and the controller name.
// You should be redirecting to a GET action method anyways. Anyone can provide a better way of achieving this.
var controllerActionDescriptor = actionDescriptorsProvider.ActionDescriptors.Items
.Where(s => s is ControllerActionDescriptor bb
&& bb.ActionName == actionName
&& bb.ControllerName == controllerName
&& (bb.ActionConstraints == null
|| (bb.ActionConstraints != null
&& bb.ActionConstraints.Any(x => x is HttpMethodActionConstraint cc
&& cc.HttpMethods.Contains(HttpMethods.Get)))))
.Select(s => s as ControllerActionDescriptor)
.FirstOrDefault();
if (controllerActionDescriptor is null) throw new Exception($"You were supposed to be redirected to {actionName} but the action descriptor could not be found.");
// Create a new route endpoint
// The route pattern is not needed but MUST be present.
var routeEndpoint = new RouteEndpoint(currentEndpoint.RequestDelegate, RoutePatternFactory.Parse(""), 1, new EndpointMetadataCollection(new object[] { controllerActionDescriptor }), controllerActionDescriptor.DisplayName);
// set the new endpoint. You are assured that the previous endpoint will never execute.
context.SetEndpoint(routeEndpoint);
}
Important
You must make the view of the action method available by placing it in the Shared folder. Alternatively, you may decide to provide a custom implementation of IViewLocationExpander
Before accessing the endpoint, the routing middleware must have executed.
USAGE
public static void ChangeDefaultPassword(this HttpContext context)
=> context.RedirectToPath("Account","ChangePassword");
Check your middleware order.
The middleware exposed by .UseRouting() is what's responsible for deciding which endpoint to hit based on the incoming request path. If your path rewrite middleware comes later in the pipeline (like mine was), it'll be too late and the routing decision has been made.
Moving my custom middleware before UseRouting() ensured that the path was set as I needed it before the routing middleware had been hit.
public void Configure(IApplicationBuilder app, IWebHostEnvironment env, TelemetryConfiguration telemetryConfig)
{
//snip
app.UseMiddleware<PathRewritingMiddleware>();
app.UseRouting();
app.UseEndpoints(endpoints =>
{
endpoints.MapControllers();
});
//snip
}
I had a similar reroute issue. In my case, I want to reroute users to a "you don't have permissions" view when an AuthorationHandler fails. I applied the following code, notably (httpContext.Response.Redirect(...)) in (.Net Core 3.1) to route me to a NoPermissions action on a Home Controller.
In the handler class:
protected override async Task HandleRequirementAsync(AuthorizationHandlerContext context, FooBarRequirement requirement) {
var hasAccess = await requirement.CheckAccess(context.User);
if (hasAccess)
context.Succeed(requirement);
else {
var message = "You do not have access to this Foobar function";
AuthorizeHandler.NoPermission(mHttpContextAccessor.HttpContext, context, requirement, message);
}
}
I wrote a static class to handle the redirect, passing in the url expected by the controller and action plus an error message, and the redirect permanent flag set to true:
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Http;
namespace Foo.BusinessLogic.Security {
public static class AuthorizeHandler {
public static void NoPermission(HttpContext httpContext,
AuthorizationHandlerContext context, IAuthorizationRequirement requirement, string
errorMessage) {
context.Succeed(requirement);
httpContext.Response.Redirect($"/home/nopermission/?m={errorMessage}", true);
}
}
}
Finally, the controller and action that handles the view and message
[AllowAnonymous]
public IActionResult NoPermission(string m) {
return View("NoPermission", m);
}
}
In my case, I am manually selecting the matching endpoint in a DynamicRouteValueTransformer. I have a mostly working solution but have to switch to other priorities. Perhaps someone else can create a more elegant solution using built in Action executors.
RequestDelegate requestDelegate = async (HttpContext x) =>
{//manually handle controller activation, method invocation, and result processing
var actionContext = new ActionContext(x, new RouteData(values), new ControllerActionDescriptor() { ControllerTypeInfo = controllerType.GetTypeInfo() });
var activator = x.RequestServices.GetService(typeof(IControllerActivator)) as ServiceBasedControllerActivator;
var controller = activator.Create(new ControllerContext(actionContext));
var arguments = methodInfo.GetParameters().Select(p =>
{
object r;
if (requestData.TryGetValue(p.Name, out object value)) r = value;
else if (p.ParameterType.IsValueType) r = Activator.CreateInstance(p.ParameterType);
else r = null;
return r;
});
var actionResultTask = methodInfo.Invoke(controller, arguments.ToArray());
var actionTask = actionResultTask as Task<IActionResult>;
if (actionTask != null)
{
var actionResult = await actionTask;
await actionResult.ExecuteResultAsync(actionContext);//errors here. actionContext is incomplete
}
};
var endpoint = new Endpoint(requestDelegate, EndpointMetadataCollection.Empty, methodInfo.Name);
httpContext.SetEndpoint(endpoint);

How can i unit test an EntitySetController

i try to unit test an EntitySetController. I can test Get but have problems in testing the Post Method.
I played around with SetODataPath and SetODataRouteName but when i call this.sut.Post(entity) i get a lot of errors regarding missing Location Header, missing OData-Path, missing Routes.
I am at my wit's end.
Is there anybody out there who has successfully testet their EntitySetController?
Has anybody an idea for me?
Maybe should i test only the protected overrided methods from my EntitySetController implementation? But how can i test protected methods?
Thanks for your help
Came here looking for a solution aswell. This seems to work however not sure if there is a better way.
The controller needs a minimum of CreateEntity and GetKey overrides:
public class MyController : EntitySetController<MyEntity, int>
{
protected override MyEntity CreateEntity(MyEntity entity)
{
return entity;
}
protected override int GetKey(MyEntity entity)
{
return entity.Id;
}
}
Where MyEntity is really simple:
public class MyEntity
{
public int Id { get; set; }
public string Name { get; set; }
}
Looks like you need at least:
+ Request with a URI
+ 3 keys in the request header, MS_HttpConfiguration, MS_ODataPath and MS_ODataRouteName
+ A HTTP configuration with a route
[TestMethod]
public void CanPostToODataController()
{
var controller = new MyController();
var config = new HttpConfiguration();
var request = new HttpRequestMessage();
config.Routes.Add("mynameisbob", new MockRoute());
request.RequestUri = new Uri("http://www.thisisannoying.com/MyEntity");
request.Properties.Add("MS_HttpConfiguration", config);
request.Properties.Add("MS_ODataPath", new ODataPath(new EntitySetPathSegment("MyEntity")));
request.Properties.Add("MS_ODataRouteName", "mynameisbob");
controller.Request = request;
var response = controller.Post(new MyEntity());
Assert.IsNotNull(response);
Assert.IsTrue(response.IsSuccessStatusCode);
Assert.AreEqual(HttpStatusCode.Created, response.StatusCode);
}
I'm not too sure about the IHttpRoute, in the aspnet source code (I had to link to this to figure this all out) the tests use mocks of this interface. So for this test I just create a mock of this and implement the RouteTemplate property and GetVirtualPath method. All the others on the interface were not used during the test.
public class MockRoute : IHttpRoute
{
public string RouteTemplate
{
get { return ""; }
}
public IHttpVirtualPathData GetVirtualPath(HttpRequestMessage request, IDictionary<string, object> values)
{
return new HttpVirtualPathData(this, "www.thisisannoying.com");
}
// implement the other methods but they are not needed for the test above
}
This is working for me however I am really not too sure about the ODataPath and IHttpRoute and how to set it correctly.
In addition to the answer from #mynameisbob, I have found you also may need to set the HttpRequestContext as well on the Request properties:
var requestContext = new HttpRequestContext();
requestContext.Configuration = config;
request.Properties.Add(HttpPropertyKeys.RequestContextKey, requestContext);
I needed the above additions for example when creating an HttpResponseMessage as follows:
public virtual HttpResponseException NotFound(HttpRequestMessage request)
{
return new HttpResponseException(
request.CreateResponse(
HttpStatusCode.NotFound,
new ODataError
{
Message = "The entity was not found.",
MessageLanguage = "en-US",
ErrorCode = "Entity Not Found."
}
)
);
}
Without having the HttpRequestContext set, the above method will throw an Argument Null Exception as the CreateResponse extension method attempts to get the HttpConfiguration from the HttpRequestContext (rather than directly from the HttpRequest).
OK updated answer.
I've also found to support executing a returned IHttpActionResult successfully, a few more things are needed.
Here is the best approach I found so far, I'm sure there is a better way but this works for me:
// Register OData configuration with HTTP Configuration object
// Create an ODataConfig or similar class in App_Start
ODataConfig.Register(config);
// Get OData Parameters - suggest exposing a public GetEdmModel in ODataConfig
IEdmModel model = ODataConfig.GetEdmModel();
IEdmEntitySet edmEntitySet = model.EntityContainers().Single().FindEntitySet("Orders");
ODataPath path = new ODataPath(new EntitySetPathSegment(edmEntitySet));
// OData Routing Convention Configuration
var routingConventions = ODataRoutingConventions.CreateDefault();
// Attach HTTP configuration to HttpRequestContext
requestContext.Configuration = config;
// Attach Request URI
request.RequestUri = requestUri;
// Attach Request Properties
request.Properties.Add(HttpPropertyKeys.HttpConfigurationKey, config);
request.Properties.Add(HttpPropertyKeys.RequestContextKey, requestContext);
request.Properties.Add("MS_ODataPath", path);
request.Properties.Add("MS_ODataRouteName", "ODataRoute");
request.Properties.Add("MS_EdmModel", model);
request.Properties.Add("MS_ODataRoutingConventions", routingConventions);
request.Properties.Add("MS_ODataPathHandler", new DefaultODataPathHandler());
Also, to get the correct Location header values etc, you really want to call your Web Api application OData configuration code.
So rather than using:
config.Routes.Add("mynameisbob", new MockRoute());
You should separate the portion of the WebApiConfig class that sets up your OData routes into a separate class (e.g. ODataConfig) and use that to register the correct routes for your tests:
e.g.
ODataConfig.Register(config);
The only things you then have to watch out for is that the following lines match your routing configuration:
request.Properties.Add("MS_ODataPath", new ODataPath(new EntitySetPathSegment("MyEntity")));
request.Properties.Add("MS_ODataRouteName", "mynameisbob");
So if your Web API OData configuration is as follows:
config.Routes.MapODataRoute("ODataRoute", "odata", GetEdmModel());
private static IEdmModel GetEdmModel()
{
ODataModelBuilder modelBuilder = new ODataConventionModelBuilder();
modelBuilder.EntitySet<MyEntity>("MyEntities");
IEdmModel model = modelBuilder.GetEdmModel();
return model;
}
Then this is the correct configuration:
request.Properties.Add("MS_ODataPath", new ODataPath(new EntitySetPathSegment("MyEntities")));
request.Properties.Add("MS_ODataRouteName", "ODataRoute");
With this in place, your Location header will be generated correctly.
In addition to everything here, I had to manually attach the context to the request, as well as create route data. Unfortunately there is no way I found to unit-test without a dependency on route/model configuration.
So using a route called "ODataRoute" which is all part of the normal configuration established in my static ODataConfig.Configure() method (same as above, it creates the model and calls a bunch of MapODataServiceRoute), the following code works to prepare a controller for a test:
protected static void SetupControllerForTests(ODataController controller,
string entitySetName, HttpMethod httpMethod)
{
//perform "normal" server configuration
var config = new HttpConfiguration();
ODataConfig.Configure(config);
//set up the request
var request = new HttpRequestMessage(httpMethod,
new Uri(string.Format("http://localhost/odata/{0}", entitySetName)));
//attach it to the controller
//note that this will also automagically attach a context to the request!
controller.Request = request;
//get the "ODataRoute" route from the configuration
var route = (ODataRoute)config.Routes["ODataRoute"];
//extract the model from the route and create a path
var model = route.PathRouteConstraint.EdmModel;
var edmEntitySet = model.FindDeclaredEntitySet(entitySetName);
var path = new ODataPath(new EntitySetPathSegment(edmEntitySet));
//get a couple more important bits to set in the request
var routingConventions = route.PathRouteConstraint.RoutingConventions;
var pathHandler = route.Handler;
//set the properties of the request
request.SetConfiguration(config);
request.Properties.Add("MS_ODataPath", path);
request.Properties.Add("MS_ODataRouteName", "ODataRoute");
request.Properties.Add("MS_EdmModel", model);
request.Properties.Add("MS_ODataRoutingConventions", routingConventions);
request.Properties.Add("MS_ODataPathHandler", pathHandler);
//set the configuration in the request context
var requestContext = (HttpRequestContext)request.Properties[HttpPropertyKeys.RequestContextKey];
requestContext.Configuration = config;
//get default route data based on the generated URL and add it to the request
var routeData = route.GetRouteData("/", request);
request.SetRouteData(routeData);
}
This took me the better part of a few days to piece together, so I hope this at least saves someone else the same.

Alfresco: Custom Share Evaluator based on some custom repo webscripts

So I'd like to write a new set of evaluators in Share based on the result of some repository webscripts.
The current existing Share evaluators are usable through some XML configuration and are related to Alfresco usual meta-data.
But, I'd like to know how to write my own Java evaluator while re using most of the logic already here (BaseEvaluator).
Suppose I have a repository webscript that returns some JSON like {"result" : "true"}:
How do I access it from my custom Evaluator? Mainly how do I access the proxy URL to alfresco webapp from the Spring context?
Do I need to write my own async call in Java?
Where do I find this JSONObject parameter of the required evaluate method?
thanks for your help
See if this helps. This goes into a class that extends BaseEvaluator. Wire the bean in through Spring, then set the evaluator on your actions.
public boolean evaluate(JSONObject jsonObject) {
boolean result = false;
final RequestContext rc = ThreadLocalRequestContext.getRequestContext();
final String userId = rc.getUserId();
try {
final Connector conn = rc.getServiceRegistry().getConnectorService().getConnector("alfresco", userId, ServletUtil.getSession());
final Response response = conn.call("/someco/example?echo=false");
if (response.getStatus().getCode() == Status.STATUS_OK) {
System.out.println(response.getResponse());
try {
org.json.JSONObject json = new org.json.JSONObject(response.getResponse());
result = Boolean.parseBoolean(((String) json.get("result")));
} catch (JSONException je) {
je.printStackTrace();
return false;
}
} else {
System.out.println("Call failed, code:" + response.getStatus().getCode());
return false;
}
} catch (ConnectorServiceException cse) {
cse.printStackTrace();
return false;
}
return result;
}
In this example I am using a simple example web script that echoes back your JSON and switches the result based on the value of the "echo" argument. So when it is called with "false", the JSON returns false and the evaluator returns false.
I should probably point out the name collision between the org.json.simple.JSONObject that the evaluate method expects and the org.json.JSONObject I am using to snag the result from the response JSON.

nopCommerce on AppHarbor. Redirect loop

I am trying to deploy a nopCommerce application to AppHarbor.
When I start the page I run into a runtime redirect loop however. I added a bit of debug logging and the problem seems to be this part in Global.asax.cs -> EnsureDatabaseIsInstalled():
if (!webHelper.GetThisPageUrl(false).StartsWith(installUrl, StringComparison.InvariantCultureIgnoreCase))
{
this.Response.Redirect(installUrl);
}
StartsWith comparison is always false because
GetThisPageUrl returns
http://[name].apphb.com:14275/install
and installUrl (via GetStoreLocation) returns
http://[name].apphb.com/install
Has anyone been able to make nopCommerce work with AppHarbor at all?
It looks like you will need to modify nopCommerce to omit the port number. I took a quick look at the source and there seems to be two possible solutions:
1) Changing the boolean argument from false to true in the EnsureDatabaseIsInstalled method should cause the GetThisPageUrl method to pick a different branch that generates the URL without the port number.
2) Updating the else branch in the GetThisPageUrl method (of "WebHelper.cs") to ignore the port number.
It's easier to pick the first solution, but patching the issue at its core will be better so you don't run into similar issues.
In addition to #TroelsThomsen fix, we use a wrapper in our base controller to ensure that all of our code is oblivious to appharbor port changing.
First, #TroelsThomsen fix in Webhelper.cs:75
public virtual string GetThisPageUrl(bool includeQueryString, bool useSsl)
{
string url = string.Empty;
if (_httpContext == null)
return url;
if (includeQueryString)
{
string storeHost = GetStoreHost(useSsl);
if (storeHost.EndsWith("/"))
storeHost = storeHost.Substring(0, storeHost.Length - 1);
url = storeHost + _httpContext.Request.RawUrl;
}
else
{
#if DEBUG
var uri = _httpContext.Request.Url;
#else
//Since appharbor changes port number due to multiple servers, we need to ensure port = 80 as in AppHarborRequesWrapper.cs
var uri = new UriBuilder
{
Scheme = _httpContext.Request.Url.Scheme,
Host = _httpContext.Request.Url.Host,
Port = 80,
Path = _httpContext.Request.Url.AbsolutePath,
Fragment = _httpContext.Request.Url.Fragment,
Query = _httpContext.Request.Url.Query.Replace("?", "")
}.Uri;
#endif
url = uri.GetLeftPart(UriPartial.Path);
}
url = url.ToLowerInvariant();
return url;
}
So what we did is simply add files from https://gist.github.com/1158264 into Nop.Core\AppHarbor
and modified base controllers:
nopcommerce\Presentation\Nop.Web\Controllers\BaseNopController.cs
public class BaseNopController : Controller
{
protected override void Initialize(RequestContext requestContext)
{
//Source: https://gist.github.com/1158264
base.Initialize(new RequestContext(new AppHarborHttpContextWrapper(System.Web.HttpContext.Current),
requestContext.RouteData));
}
//Same file from here downwards...
}
nopcommerce\Presentation\Nop.Web.Admin\Controllers\BaseNopController.cs
public class BaseNopController : Controller
{
protected override void Initialize(System.Web.Routing.RequestContext requestContext)
{
//set work context to admin mode
EngineContext.Current.Resolve<IWorkContext>().IsAdmin = true;
//Source: https://gist.github.com/1158264
base.Initialize(new RequestContext(new AppHarborHttpContextWrapper(System.Web.HttpContext.Current), requestContext.RouteData));
//base.Initialize(requestContext);
}
//Same file from here downwards...
}

Resources