I have created an ImageController to serve images from a location in my project. Now I'm trying to get it to route from "/Image/file.png" to my Controller, but I can't seem to get it right. The controller is never been called (I set a breakpoint in the first line of the action and it never breaks).
My route:
routes.MapRoute(
"Image",
"Image/{file}",
new { controller = "Image", action = "Render", file = "" }
);
My ImageController:
public class ImageController : Controller
{
//
// GET: /Image/
public ActionResult Render(string file)
{
var path = this.getPath(file);
System.Diagnostics.Debug.WriteLine(path);
if (!System.IO.File.Exists(path))
{
return new HttpNotFoundResult(string.Format("File {0} not found.", path));
}
return new ImageResult(path);
}
private string getPath(string file)
{
return string.Format("{0}/{1}", Server.MapPath("~/Content/Images"), file);
}
}
Why isn't my project routing from "Images/{file}" to my controller?
It's probably because the request is being routed to the static file handler due to the .png extension in the url. You could try passing the filename and suffix as separate parameters, something like this:
routes.MapRoute(
"Image",
"Image/{filename}/{suffix}",
new { controller = "Image", action = "Render", filename = "", suffix = "" }
);
Your controller action then becomes:
public ActionResult Render(string filename, string suffix)
And it should match urls like this:
/Image/file/png
Related
I have a generic catch all controller/action that receive files, parse the json content and find out the controller name and action name to be called from that.
Here my previous .NET Framework (old ASP) implementation which worked great:
public async Task<ActionResult> Run(PackingSlip packingSlip, IEnumerable<HttpPostedFileBase> files)
{
var controllerName = packingSlip.service_name;
var actionName = packingSlip.service_object;
// get the controller
var ctrlFactory = ControllerBuilder.Current.GetControllerFactory();
var ctrl = ctrlFactory.CreateController(this.Request.RequestContext, controllerName) as Controller;
var ctrlContext = new ControllerContext(this.Request.RequestContext, ctrl);
var ctrlDescAsync = new ReflectedAsyncControllerDescriptor(ctrl.GetType());
ctrl.ControllerContext = ctrlContext;
// get the action
var actionDesc = ctrlDescAsync.FindAction(ctrlContext, actionName);
// execute
ActionResult result;
if (actionDesc is AsyncActionDescriptor actionDescAsync)
result = await Task.Factory.FromAsync((asyncCallback, asyncState) => actionDescAsync.BeginExecute(ctrlContext, new Dictionary<string, object> { { "packingSlip", packingSlip }, { "files", files } }, asyncCallback, asyncState), asyncResult => actionDescAsync.EndExecute(asyncResult), null) as ActionResult;
else
result = actionDesc.Execute(ctrlContext, new Dictionary<string, object> { { "packingSlip", packingSlip }, { "files", files } }) as ActionResult;
// return the other action result as the current action result
return result;
}
Now with ASP.NET Core (or .NET 5), ControllerBuilder doesn't exist anymore and most of those things changed.
I tried to inject a IControllerFactory and use it, but can't find the proper way to use it to call an action knowing the "controllerName" and "actionName". It should also, like before, determine if it was an async action or not and act accordingly.
Found the answer by myself.
AspCore have an hidden barely documented extension method that registers controllers in the DI container: AddControllersAsServices.
services.AddMvc().AddControllersAsServices();
Then you can use IServiceProvider to resolve your controllers.
I'm trying to setup a single ASP.NET Core Razor Web app localized for use on multi domains. I have the localization working, with one different language for each domain. But right now I want to have the .com domain accepting a routing parameter, to make the URL path decide with language to show.
Something like:
www.mysite.pt - no custom routing - www.mysite.pt/PageA works, localized in Portuguese.
www.mysite.com - custom routing - www.mysite.com/us/PageA goes to PageA, localized in en-US. But www.mysite.com/PageA should return a 404, as for this domain every page needs the country parameter.
For MVC this could be achieved by using the MapRoute with a custom IRouteConstraint to filter by domain.
However with Razor pages, I only see the option to go with the conventions and add a class derived from IPageRouteModelConvention.
But I don't see a way on the IPageRouteModelConvention methodology to use a IRouteConstraint.
Is there a way to do this?
Not exactly the best solution... but worked this out:
On ConfigureServices added a custom convention that takes a country parameter only with two country codes US and CA:
options.Conventions.Add(new CountryTemplateRouteModelConvention());
wethe this class being:
public class CountryTemplateRouteModelConvention : IPageRouteModelConvention
{
public void Apply(PageRouteModel model)
{
var selectorCount = model.Selectors.Count;
for (var i = 0; i < selectorCount; i++)
{
var selector = model.Selectors[i];
// selector.AttributeRouteModel.SuppressLinkGeneration = false;
//we are not adding the selector, but replacing the existing one
model.Selectors.Add(new SelectorModel
{
AttributeRouteModel = new AttributeRouteModel
{
Order = -1,
Template = AttributeRouteModel.CombineTemplates(#"{country:length(2):regex(^(us|ca)$)}", selector.AttributeRouteModel.Template),
}
});
}
}
}
Then, before the UseMvc on Configure, I used two types of Rewrite rules:
var options = new RewriteOptions();
options.Add(new CountryBasedOnDomainRewriteRule(domains: GetDomainsWhereCountryComesFromDomain(Configuration)));
options.Add(new CountryBasedOnPathRewriteRule(domains: GetDomainsWhereCountryComesFromPath(Configuration)));
app.UseRewriter(options);
The methods GetDomainsWhereCountryComesFromDomain and GetDomainsWhereCountryComesFromPath just read from the appsettings the domains where I want to have a single language, and the domains where I want the language to be obtained from the URL path.
Now, the two IRule classes:
public class CountryBasedOnPathRewriteRule : IRule
{
private readonly string[] domains;
public CountryBasedOnPathRewriteRule(string[] domains)
{
this.domains = domains;
}
public void ApplyRule(RewriteContext context)
{
string hostname = context.HttpContext.Request.Host.Host.ToLower();
if (!domains.Contains(hostname)) return;
//only traffic that has the country on the path is valid. examples:
// www.mysite.com/ -> www.mysite.com/US/
// www.mysite.com/Cart -> www.mysite.com/US/Cart
var path = context.HttpContext.Request.Path.ToString().ToLower();
/* let's exclude the error page, as method UseExceptionHandler doesn't accept the country parameter */
if (path == "/" || path == "/error")
{
//redirect to language default
var response = context.HttpContext.Response;
response.StatusCode = (int)HttpStatusCode.Moved;
response.Headers[HeaderNames.Location] = "/us/"; //default language/country
context.Result = RuleResult.EndResponse;
}
string pathFirst = path.Split('/')?[1];
if (pathFirst.Length != 2) /* US and CA country parameter is already enforced by the routing */
{
var response = context.HttpContext.Response;
response.StatusCode = (int)HttpStatusCode.NotFound;
context.Result = RuleResult.EndResponse;
}
}
}
public class CountryBasedOnDomainRewriteRule : IRule
{
private readonly string[] domains;
public CountryBasedOnDomainRewriteRule(string[] domains)
{
this.domains = domains;
}
public void ApplyRule(RewriteContext context)
{
string hostname = context.HttpContext.Request.Host.Host.ToLower();
if (!domains.Contains(hostname)) return;
var path = context.HttpContext.Request.Path.ToString().ToLower();
string pathFirst = path.Split('/')?[1];
if (pathFirst.Length == 2) //we are trying to use www.mysite.co.uk/us which is not allowed
{
var response = context.HttpContext.Response;
response.StatusCode = (int)HttpStatusCode.NotFound;
context.Result = RuleResult.EndResponse;
}
}
}
And that's it.
in my app I've generated an url like this:
http://www.test.com/?mail=test%40gmail.ba&code=71147ff9-87ae-41fc-b53f-5ecb3dbe5a01
The way how I generated Url is posted below:
private string GenerateUrl(string longUrl, string email, string confirmCode)
{
try
{
// By the way this is not working (Home/MailConfirmed) I'm getting message
// Requested URL: /Home/MailConfirmed
// The resource cannot be found.
string url = longUrl + "/Home/MailConfirmed";
var uriBuilder = new UriBuilder(url);
var query = HttpUtility.ParseQueryString(uriBuilder.Query);
query["mail"] = email;
query["code"] = confirmCode;
uriBuilder.Query = query.ToString();
uriBuilder.Port = -1;
url = uriBuilder.ToString();
return url;
}
catch (Exception ex)
{
return "Error happened: " + ex.Message;
}
}
In longUrl I'm passing www.test.com, in email I'm passing
test#gmail.com and so on..
There are informations about my website:
www.test.com
mail:test#gmail.com
confirmcode:71147ff9-87ae-41fc-b53f-5ecb3dbe5a01
And in my HomeController.cs there is a method which should took parameters out of query string - url and pass it to the method which should activate users account by getting user by mail (mail is unique) and comparing this guid with guid in database. So I'm wondering how can I call this method?
So my method looks like this:
public JsonResult MailConfirmed(string mail, string confirmCode)
{
try
{
// Here I will get user and update it in DB
return Json("success", JsonRequestBehavior.AllowGet);
}
catch(Exception ex)
{
return Json("fail", JsonRequestBehavior.AllowGet);
}
}
So my question is how is possiblee for user to click on following link and to get an my method invoked.. ?
Thanks a lot
Cheers
In order to navigate to your MailConfirmed(), your url would need to be
http://www.test.com/Home/MailConfirmed?mail=test%40gmail.ba&confirmcode=71147ff9-87ae-41fc-b53f-5ecb3dbe5a01
Note the segments for the controller and action names, and code=xxx should be confirmcode=xxx to match the name of the parameter in the method.
You can simplify your code (and delete your GenerateUrl() method) by making use of UrlHelper methods to generate the url).
To generate the above url, all you need in your controller method is
string url = Url.Action("MailConfirmed", "Home",
new { mail = email, confirmcode = confirmCode },
this.Request.Url.Scheme);
In My Reserve ApiController, I need to have may BankRedirect action's path in a string and Url.Route has been used but it doesn't work.
public string GoToBank(string token, string username )
{
string path Url.Route("BankRedirect", new { controller = "Reserve"} , new { userId = "" }))
return path;
}
[Route("BankRedirect")]
[HttpPost]
[BasicAuthenticationFilter]
public async Task<UpdateResult<string>> BankRedirect( [FromBody]string userId)
{
}
The corresponded path for decorating action with [Route("BankRedirect")] is /BankRedirectץ
The given Url.Route output is Reserve/BankRedirect.
/BankRedirect != Reserve/BankRedirect
You should change one of them, either:
[Route("Reserve/BankRedirect")]
Or
return "BankRedirect";
I have an area called Advert. This area as two route rules. But unfortunately only one of the route rules works at a time. Usually the first rule the other below would not work. but if I interswitch rules the one that comes first would work.
I would paste the rule and html links that call the rules.
Code snippet from AdvertAreaRegistration.cs
public override void RegisterArea(AreaRegistrationContext context)
{
context.MapRoute(
"Advert_second",
"Advert/{controller}/{action}",
new { controller = "AdvertAdmin", action = "Index" },
namespaces: new string[] { "LiveChatPrototype.Mvc.Areas.Advert.Controllers" }
);
context.MapRoute(
name: "Advert_default",
url: "Advert/{id}/{advertid}",
defaults: new { controller = "Advertisement", action = "Index", id =UrlParameter.Optional, advertid = UrlParameter.Optional },
namespaces: new string[] { "LiveChatPrototype.Mvc.Areas.Advert.Controllers" }
);
}
The html links which I use to call my rules
This is for the first rule.
This is for the second rule.
Either of the links would work if it rule comes first.
Please how can I make both rules work at the same time.
Currently the two route segments are identical
"Advert/{controller}/{action}"
"Advert/{id}/{advertid}"
Both have two dynamic segments, so asp.net mvc cannot distinguish between the two and matches the first one.
But it seems like the segments in "Advert/{id}/{advertid}" id and advertid are intended to be integers. Then you can add regex constraint in the route to distinguish between them.
like
public override void RegisterArea(AreaRegistrationContext context)
{
context.MapRoute(
name: "Advert_default",
url: "Advert/{id}/{advertid}",
defaults: new { controller = "Advertisement", action = "Index" },
constraints: new { id = #"\d+", advertid = #"\d+" }
namespaces: new string[] { "LiveChatPrototype.Mvc.Areas.Advert.Controllers" }
);
context.MapRoute(
"Advert_second",
"Advert/{controller}/{action}",
new { controller = "AdvertAdmin", action = "Index" },
namespaces: new string[] { "LiveChatPrototype.Mvc.Areas.Advert.Controllers" }
);
}
Hope this helps