route doesn't work at production environment - asp.net-mvc-3

I have created following route
routes.MapRoute("ThumbnailRoute",// Route name
"Image/{action}/{session}/{parentId}/{fileName}/{ctype}/{thumbNailSize}", // URL with parameters
new { controller = "Image", action = "GenerateThumbnail", session = "", parentId = "", fileName = "", ctype = "", thumbNailSize = 70 }, // Parameter defaults
new { controller = #"[^\.]*", action = #"[^\.]*" });
and my extension method returns a string like following which will be the src attribute of the img tag:
return string.Format("/{0}/{1}/{2}/{3}/{4}/{5}/{6}", controller, action, session, parentId, fileName, ctype, thumbNailSize);
when I right click on the pages and choose properties for both dev and prod environments the src av img tag is same (http://localhost/Image/GenerateThumbnail/de-DE/121/0beac6da-7c09-4faf-ad4b-48326f9d337e.jpg/jpeg/70) only different is the domain name (localhost, www.domain.com) but the images de not appear on prod. thanks for your help

If the URLs look fine on the production version - perhaps the issue isn't with the routing, but rather with the code in the action method.
Have you checked what response you get from the browser when hitting the production URL?
What response do you get when your browse to http://www.domain.com/Image/GenerateThumbnail/de-DE/121/0beac6da-7c09-4faf-ad4b-48326f9d337e.jpg/jpeg/70?

Never hardcode urls as you did. Always use url helpers. The thing is that when you deploy your application in IIS there's a virtual directory name. So the correct url is the following:
http://foo.com/MyAppName/Image/GenerateThumbnail/de-DE/121/0beac6da-7c09-4faf-ad4b-48326f9d337e.jpg/jpeg/70
instead of:
http://foo.com/Image/GenerateThumbnail/de-DE/121/0beac6da-7c09-4faf-ad4b-48326f9d337e.jpg/jpeg/70
Since you have hardcoded the url you get 404.
So use the RouteUrl method to generate it which will take into account this virtual directory if any. Don't use any string formatting to build urls:
public ActionResult Index()
{
string url = Url.RouteUrl("ThumbnailRoute", new
{
action = "GenerateThumbnail",
controller = "Image",
session = session,
parentId = parentId,
fileName = fileName,
ctype = ctype,
thumbNailSize = thumbNailSize
});
...
}

Related

Modify RewriteContext Host in NetCore 2 rewriting middleware

I need to rewrite not redirect certain requests to GET files to an external storage location (hosted on a different domain)
I am able to accomplish this with redirecting using rewriter middleware, however i would like to rewrite instead.
I need to use some logic to rewrite to a specific file location, so I followed https://learn.microsoft.com/en-us/aspnet/core/fundamentals/url-rewriting?view=aspnetcore-2.0&tabs=aspnetcore2x#method-based-rule tutorial.
When i set the Host and path i can see the absolute url looks correct but when the method exits i get "No webpage was found for the web address:" and url displayed is the original request Url
RewriteRule.cs (also holds redirect url logic in ApplyRule() that i don't want to use)
public static void RewriteStorageFileRequests(RewriteContext context)
{
var request = context.HttpContext.Request;
var path = request.Path.Value;
if (path.Contains("foo/bar"))
{
context.Result = RuleResult.SkipRemainingRules;
request.Host = new HostString("storage/space");
request.Path = path.Replace("foo/bar", string.Empty);
var test2 = request.GetDisplayUrl();
}
}
So essentially, if a call comes in as:
https://localhost:44327/foo/bar/bf34d911-03db-5tda-9c99-c7dd96593159/w3.jpg
I want to rewrite it to:
https://storage/space/bf34d911-03db-5tda-9c99-c7dd96593159/w3.jpg
Startup.cs
app.UseRewriter(new RewriteOptions().Add(RewriteUrlRule.RewriteStorageFileRequests));
test2 displays the correct url in the debugger.
I tried setting host to empty string and passing host value (storage/space) in path but still no luck.
Before Rewriter
After Rewrite
UPDATE: I was able to get rewrite to work by changing
context.Result = RuleResult.SkipRemainingRules;
to
context.Result = RuleResult.EndResponse;
However, when i get to the Url it returns 200OK but does not show the image.
(see attached file)
So, right now the only way i can get images is still with Redirect
Rewrite GET

Access TempData within custom middleware

I have custom middleware that provides global error handling. If an exception is caught it should log the details with a reference number. I then want to redirect the user to an error page and only show the reference number. My research shows that TempData should be ideal for this but it only seems to be accessible from within a controller context. I tried adding the reference number to HttpContext.Items["ReferenceNumber"] = Guid.NewGuid();
But this value is lost through the redirect.
How can middleware pass information through a redirect? Do I just have to put the number in a querystring?
Inside the middleware class you need to add a reference to get access to the required interfaces. I have this middleware in a separate project and needed to add this NuGet package.
using Microsoft.AspNetCore.Mvc.ViewFeatures;
This then allows you to request the correct services within the middleware.
//get TempData handle
ITempDataDictionaryFactory factory = httpContext.RequestServices.GetService(typeof(ITempDataDictionaryFactory)) as ITempDataDictionaryFactory;
ITempDataDictionary tempData = factory.GetTempData(httpContext);
After you have ITempDataDictionary you can use it like you would use TempData within a controller.
//pass reference number to error controller
Guid ReferenceNum = Guid.NewGuid();
tempData["ReferenceNumber"] = ReferenceNum.ToString();
//log error details
logger.LogError(eventID, exception, ReferenceNum.ToString() + " - " + exception.Message);
Now when I get the the controller after a redirect I have no issues pulling out the reference number and using it in my view.
//read value in controller
string refNum = TempData["ReferenceNumber"] as string;
if (!string.IsNullOrEmpty(refNum))
ViewBag.ReferenceNumber = refNum;
#*display reference number if one was provided*#
#if (ViewBag.ReferenceNumber != null){<p>Reference Number: #ViewBag.ReferenceNumber</p>}
Once you put this all together, you give users a reference number that they can give you to help troubleshoot the problem. But, you are not passing back potentially sensitive error information which could be misused.
You can register an ITempDataProvider yourself and use it in your middleware. Here is a small sample I got working between two simple paths. If you are already using MVC the ITempDataProvider is probably already registered. The issue I faced was the path of the cookie that was written. It was /page1 so /page2 did not have access to the cookie. So I had to override the options as you can see in code below.
I hope this will help you :)
public void ConfigureServices(IServiceCollection services)
{
services.AddSingleton<IDataProtectionProvider>(s => DataProtectionProvider.Create("WebApplication2"));
services.Configure<CookieTempDataProviderOptions>(options =>
{
options.Path = "/";
});
services.AddSingleton<ITempDataProvider, CookieTempDataProvider>();
}
public void Configure(IApplicationBuilder app, IHostingEnvironment env, ITempDataProvider tempDataProvider)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
app.Map("/page1", (app1) =>
{
app1.Run(async context =>
{
tempDataProvider.SaveTempData(context, new Dictionary<string, object> { ["Message"] = "Hello from page1 middleware" });
await context.Response.WriteAsync("Hello World! I'm page1");
});
});
app.Map("/page2", (app1) =>
{
app1.Run(async context =>
{
var loadTempData = tempDataProvider.LoadTempData(context);
await context.Response.WriteAsync("Hello World! I'm page2: Message from page1: " + loadTempData["Message"]);
});
});
}
This led me in the right direction: https://learn.microsoft.com/en-us/aspnet/core/fundamentals/app-state#cookie-based-tempdata-provider
Happy coding! :)

Alexa skills kit, trouble getting session attributes to persist

I have been working on a skill where I am using Login With Amazon account linking so I can grab the user email address and name to use in my skill. I am doing something similar to the scoreKeeper sample, using the eventHandlers.js and the storage.js for saving items to a database. In the eventHandlers.onLaunch I am successfully getting the profile name and email address from Amazon and I save it to the session.attributes like this:
var profile = JSON.parse(body);
speechOutput="Hello, " + profile.name.split(" ")[0] + ".";
var sessionAttributes = {};
sessionAttributes = { name: profile.name, email: profile.email };
session.attributes = sessionAttributes;
console.log("Name in session:", session.attributes.name);
The console log shows the name so I know it is being saved in the session.attributes, but when I try to access the session.attributes in my storage.js or intentHandlers.js, it shows it as being empty. What am I missing? Thanks in advance. This has been driving me crazy.
I got this working by adding session to the callback of the response.ask function, like this:
ask: function (speechOutput, repromptSpeech, _session) {
this._context.succeed(buildSpeechletResponse({
session: _session,
output: speechOutput,
reprompt: repromptSpeech,
shouldEndSession: false
}));
},
It had session: this._session for saving the session, but that didn't seem to be working for me. Passing it as a variable did.
Let's say you want to obtain city name from the user and be able to access that value later in the Alexa session:
1) Pre-define your key/value pairs in the JSON response:
def build_response(session_attributes, speechlet_response):
return {
'version': '1.0',
'sessionAttributes': {
"session_attributes": {
'NameOfCity': city_name,
}
},
'response': speechlet_response
}
2) In global scope, declare the variable as an empty string:
city_name = "",
3) Whenever you update that variable within a function, make sure you reference that variable as global. For example:
def set_city_name_in_session(intent, session):
card_title = intent['name']
session_attributes = {}
should_end_session = False
if 'CityIntent' in intent['slots']:
global city_name
city_name = intent['slots']['CityIntent']['value']
4) When creating a function that needs to access this variable, pass the global variable as an argument into the function then, within the function, expose all session key/pair values like so:
def access_variables(intent, session, city_name):
card_title = intent['name']
session_attributes = {}
should_end_session = False
exposed_attributes = session['attributes']['session_attributes']
city_name_attribute_value = exposed_attributes.get('NameOfCity')
if (some_kind_of_condition):
do something awesome with city_name_attribute_value
To test, enter sample utterances into Service Simulator then check if values are persisting in your Lambda JSON response.

How to get rid of forcefully added underscore with timestamp

I'm working on a project with MVC5 and ran into an issue of MVC trying to "help" me, i think. What i'm doing is trying to tidy up views by extracting their javascript without fiddling around with the bundle config.
Messing around with generic loading in the bundle folder would be an option but i'd rather avoid that.
So here's my code, which is working:
public static string GetControllerName(this ControllerBase controller)
{
var name = controller.GetType().Name;
if (name.ToLower().EndsWith("controller"))
return name.Substring(0, name.Length - 10);
return name;
}
public static IHtmlString LoadViewScripts(this HtmlHelper helper)
{
var view = helper.ViewContext.View as RazorView;
if (view != null && !string.IsNullOrEmpty(view.ViewPath))
{
var controllerName = helper.ViewContext.Controller.GetControllerName();
var vPath = string.Format("~/Scripts/View/{0}/{1}.js", controllerName, Path.GetFileName(view.ViewPath));
var realPath = helper.ViewContext.HttpContext.Server.MapPath(vPath);
if (File.Exists(realPath))
{
var urlPath = UrlHelper.GenerateContentUrl(vPath, helper.ViewContext.HttpContext);
var lwrite = File.GetLastWriteTime(realPath);
return new HtmlString(string.Format("<script type='text/javascript' language='javascript' src='{0}?v={1}'></script>", urlPath, lwrite.Ticks));
}
}
return new HtmlString(string.Empty);
}
Result i am passing to src attribute:
http://localhost/AppName/Scripts/View/Map/SelectSite.cshtml.js?v=635569414679160391
What i am getting (network view of firefox):
http://localhost/AppName/Scripts/View/Map/SelectSite.cshtml.js?v=635569414679160391&_=1421342715176
What i am expecting (network view of firefox):
http://localhost/AppName/Scripts/View/Map/SelectSite.cshtml.js?v=635569414679160391
Does anyone know how to stop MVC from "helping" me cache/refresh things in this particular case?
Ok. So it turns out there was 2 reasons why it wasnt working for me.
The kendo property to disable caching which is enabled by default wasn't in the api. Calling it like this fixed my window issue.
content: {
url: targetUrlSiteSelect,
cache : true
}
My additional script tag still contained that the underscore element. I later on found that solution contained within a comment here:
jQuery version 1.5 - ajax - <script> tag timestamp problem
Comment by Blaise:
$.ajaxPrefilter('script', function(options) { options.cache = true; });

not a valid virtual path - when trying to return a file from a url

We download a file from our CdN and then return a url to that downloaded file to the user. I'm trying to get this implemented so that when a user clicks the download buttton, it goes and test that url to that downloaded file then forces a save prompt based on that local url.
So for example if there is a button called download on the page for a specific .pdf, we ultimately have code in our controller going to the cdn and downloading the file, zipping it then returning a url such as: http://www.ourLocalAssetServer.com/assets/20120331002728.zip
I'm not sure if you you can use the File() method to return the resource to the user as to cause a save prompt when you have a url to the file, not a system directory virtual path.
So how can I get this working with the url? I need the download button to ultimately force a save prompt on their end given a url such as what is generated per this example above? Not I am using POST, not a GET, so not sure which I should use in this case either besides this not working overall to force a save prompt. It is hitting my GetFileDownloadUrl but ultimately errors saying it's not a virtual path.
Here's my code:
#foreach (CarFileContent fileContent in ModelCarFiles)
{
using (Html.BeginForm("GetFileDownloadUrl", "Car", FormMethod.Get, new { carId = Model.CarId, userId = Model.UserId, #fileCdnUrl = fileContent.CdnUrl }))
{
#Html.Hidden("userId", Model.UserId);
#Html.Hidden("carId", Model.CarId);
#Html.Hidden("fileCdnUrl", fileContent.CdnUrl);
<p><input type="submit" name="SubmitCommand" value="download" /> #fileContent.Name</p>
}
}
public ActionResult GetFileDownloadUrl(string fileCdnUrl, int carId, int userId)
{
string downloadUrl = string.Empty;
// take the passed Cdn Url and go and download that file to one of our other servers so the user can download that .zip file
downloadUrl = GetFileZipDownloadUrl(carId, userId, fileCdnUrl;
// now we have that url to the downloaded zip file e.g. http://www.ourLocalAssetServer.com/assets/20120331002728.zip
int i = downloadUrl.LastIndexOf("/");
string fileName = downloadUrl.Substring(i);
return File(downloadUrl, "application/zip", fileName);
}
error: not a valid virtual path
This won't work except the zip file is in your virtual path.
The File method you have used here File(string, string, string) expects a fileName which will be used to create a FilePathResult.
Another option would be to download it (using WebClient.DownloadData or DownloadFile methods) and passing either the byte array or the file path (depending on which you choose).
var webClient = new Webclient();
byte[] fileData = webClient.DownloadData(downloadUrl);
return File(fileData, "application/zip", fileName);
And the lines where you get the index of "/" just to get the filename is unnecessary as you could have used:
string fileName = System.IO.Path.GetFileName(downloadUrl);

Resources