Passing subdomain info to controller in IRouteConstraint.Match - asp.net-mvc-3

In subdomain route constraint I want to pass subdomain name to controller with the code below. But in controller subdomain entry is missing (only action and controller entries exist). What is wrong here? Or should I sniff subdomain in controller itself (with the help of Request.Headers["HOST"])? What is the best practice?
public class SubdomainRouting : IRouteConstraint
{
public bool Match(HttpContextBase httpContext, Route route, string parameterName, RouteValueDictionary values, RouteDirection routeDirection)
{
//
//get subdomain here
//
if (values.ContainsKey("subdomain") == false)
{
values.Add("subdomain", subdomain);
}
return true;
}
}

I think you are interpreting the values argument the wrong way. According the MSDN, it is An object that contains the parameters for the URL. So I'm not sure if its even the same RouteDataDictionary you get in the controller.
The easiest way to get the subdomain would be from the Request.Url.Host you get in the controller.

Well I changed my routing constraint implementation. Instead of implementing IRouteConstraint, I implemented RouteBase. This way I was able to add route values:
public class SubdomainRoute : RouteBase
{
public override RouteData GetRouteData(HttpContextBase httpContext)
{
RouteData returnValue = null;
//some code here
returnValue = new RouteData(this, new MvcRouteHandler());
returnValue.Values.Add("controller", "SomeController");
returnValue.Values.Add("action", "SomeAction");
returnValue.Values.Add("key", some_value_to_pass_to_controller);
//some code here
return returnValue;
}
public override VirtualPathData GetVirtualPath(RequestContext requestContext, RouteValueDictionary values)
{
return null;
}
}

Related

How to send an array via a URI using Attribute Routing in Web API?

I'm following the article on Attribute Routing in Web API 2 to try to send an array via URI:
[HttpPost("api/set/copy/{ids}")]
public HttpResponseMessage CopySet([FromUri]int[] ids)
This was working when using convention-based routing:
http://localhost:24144/api/set/copy/?ids=1&ids=2&ids=3
But with attribute routing it is no longer working - I get 404 not found.
If I try this:
http://localhost:24144/api/set/copy/1
Then it works - I get an array with one element.
How do I use attribute routing in this manner?
The behavior you are noticing is more related to Action selection & Model binding rather than Attribute Routing.
If you are expecting 'ids' to come from query string, then modify your route template like below(because the way you have defined it makes 'ids' mandatory in the uri path):
[HttpPost("api/set/copy")]
Looking at your second question, are you looking to accept a list of ids within the uri itself, like api/set/copy/[1,2,3]? if yes, I do not think web api has in-built support for this kind of model binding.
You could implement a custom parameter binding like below to achieve it though(I am guessing there are other better ways to achieve this like via modelbinders and value providers, but i am not much aware of them...so you could probably need to explore those options too):
[HttpPost("api/set/copy/{ids}")]
public HttpResponseMessage CopySet([CustomParamBinding]int[] ids)
Example:
[AttributeUsage(AttributeTargets.Parameter, Inherited = false, AllowMultiple = false)]
public class CustomParamBindingAttribute : ParameterBindingAttribute
{
public override HttpParameterBinding GetBinding(HttpParameterDescriptor paramDesc)
{
return new CustomParamBinding(paramDesc);
}
}
public class CustomParamBinding : HttpParameterBinding
{
public CustomParamBinding(HttpParameterDescriptor paramDesc) : base(paramDesc) { }
public override bool WillReadBody
{
get
{
return false;
}
}
public override Task ExecuteBindingAsync(ModelMetadataProvider metadataProvider, HttpActionContext actionContext,
CancellationToken cancellationToken)
{
//TODO: VALIDATION & ERROR CHECKS
string idsAsString = actionContext.Request.GetRouteData().Values["ids"].ToString();
idsAsString = idsAsString.Trim('[', ']');
IEnumerable<string> ids = idsAsString.Split(',');
ids = ids.Where(str => !string.IsNullOrEmpty(str));
IEnumerable<int> idList = ids.Select(strId =>
{
if (string.IsNullOrEmpty(strId)) return -1;
return Convert.ToInt32(strId);
}).ToArray();
SetValue(actionContext, idList);
TaskCompletionSource<object> tcs = new TaskCompletionSource<object>();
tcs.SetResult(null);
return tcs.Task;
}
}

Disable ApiController at runtime

I have a ASP.NET Web API (.NET 4) application which has a few controllers. We will run several instances of the Web API application on IIS with one difference. Only certain controllers will be available under certain IIS instances. What I was thinking is to disable/unload the controllers that are not applicable to an instance when the instance starts up.
Anyone got some information that could guide me in the right direction on this?
You can put your own custom IHttpControllerActivator in by decorating the DefaultHttpControllerActivator. Inside just check for a setting and only create the controller if allowed.
When you return null from the Create method the user will receive 404 Not Found message.
My example shows a value in App Settings (App.Config or Web.Config) being checked but obviously this could any other environment aware condition.
public class YourCustomControllerActivator : IHttpControllerActivator
{
private readonly IHttpControllerActivator _default = new DefaultHttpControllerActivator();
public YourCustomControllerActivator()
{
}
public IHttpController Create(HttpRequestMessage request, HttpControllerDescriptor controllerDescriptor,
Type controllerType)
{
if (ConfigurationManager.AppSettings["MySetting"] == "Off")
{
//Or get clever and look for attributes on the controller in controllerDescriptor.GetCustomAttributes<>();
//Or use the contoller name controllerDescriptor.ControllerName
//This example uses the type
if (controllerType == typeof (MyController) ||
controllerType == typeof (EtcController))
{
return null;
}
}
return _default.Create(request, controllerDescriptor, controllerType);
}
}
You can switch your activator in like so:
GlobalConfiguration.Configuration.Services.Replace(typeof(IHttpControllerActivator), new YourCustomControllerActivator());
Update
It has been a while since I looked at this question but if I was to tackle it today I would alter the approach slightly and use a custom IHttpControllerSelector. This is called before the activator and makes for a slightly more efficient place to enable and disable controllers... (although the other approach does work). You should be able to decorate or inherit from DefaultHttpControllerSelector.
Rather than unloading the controllers, I think I'd create a custom Authorize attribute that looked at the instance information in deciding to grant authorization.
You would add the following to each controller at the class level, or you could also add this to individual controller actions:
[ControllerAuthorize (AuthorizedUserSources = new[] { "IISInstance1","IISInstance2","..." })]
Here's the code for the Attribute:
public class ControllerAuthorize : AuthorizeAttribute
{
public ControllerAuthorize()
{
UnauthorizedAccessMessage = "You do not have the required access to view this content.";
}
//Property to allow array instead of single string.
private string[] _authorizedSources;
public string UnauthorizedAccessMessage { get; set; }
public string[] AuthorizedSources
{
get { return _authorizedSources ?? new string[0]; }
set { _authorizedSources = value; }
}
// return true if the IIS instance ID matches any of the AllowedSources.
protected override bool AuthorizeCore(HttpContextBase httpContext)
{
if (httpContext == null)
throw new ArgumentNullException("httpContext");
//If no sources are supplied then return true, assuming none means any.
if (!AuthorizedSources.Any())
return true;
return AuthorizedSources.Any(ut => ut == httpContext.ApplicationInstance.Request.ServerVariables["INSTANCE_ID"]);
}
The IHttpControllerActivator implementation doesn't disable the routes defined using attribute routing , if you want to switch on/off a controller and have a default catch all route controller. Switching off using IHttpControllerActivator disables the controller but when the route is requested it doesn't hit the catch all route controller -it simply tries to hit the controller that was removed and returns no controller registered.

Url routing in mvc 3.0

I want to have below specific Url routing for my website
http://www.MyWebsite?Region=US&Area=South
based on passed Region and Area query string parameter, i want to redirect to a specific controller action.
Problems:
What should be the Url Routing ?
Whether creating a common controller action which redirects to specific action will work here?
Any help would be greatly appriciated !!
Thanks
If you want to do querystring right off the root, then you need to map routes in global this way. Inside your action you can access context and grab querystring from it. Below is my example to do twitter style usernames, but idea is the same.
routes.MapRoute("UserSettings",
"{username}/settings",
new { controller = "Home", action = "Settings", username = "" },
new { username = new NotNullOrEmptyConstraint() }
);
public class NotNullOrEmptyConstraint : IRouteConstraint
{
public bool Match(HttpContextBase httpContext, Route route, string parameterName, RouteValueDictionary values, RouteDirection routeDirection)
{
try
{
return !String.IsNullOrEmpty(values["username"].ToString());
}
catch (Exception)
{
return false;
}
}
}
public ActionResult Settings(string username){
}

Can I reuse a remote validation action in MVC3

I am using a Remote validation attribute on my view model to validate a Bank Account that is specified for my Company:
ViewModel:
[Remote("CheckDefaultBank", "Company")]
public string DefaultBank
{
This in the controller I have:
[HttpGet]
public JsonResult CheckDefaultBank(string defaultBank)
{
bool result = BankExists(defaultBank);
return Json(result, JsonRequestBehavior.AllowGet);
}
That all works well. But, I have two other banks related to my company as well. However, when the remote validation js calls the action it uses a parameter mactching the field name of "DefaultBank"... so I use that as a parameter in my action.
Is there some attribute I can add in the view so that it will use a parameter of say "bankId" on the ajax get so I don't need an action for each field which are basically exactly the same?
The goal here is to eliminate now having to have this in my controller:
[HttpGet]
public JsonResult CheckRefundBank(string refundBank)
{
bool result = BankExists(defaultBank);
return Json(result, JsonRequestBehavior.AllowGet);
}
[HttpGet]
public JsonResult CheckPayrollBank(string payrollBank)
{
bool result = BankExists(defaultBank);
return Json(result, JsonRequestBehavior.AllowGet);
}
I was hoping I could do something like this in the view:
#Html.EditorFor(model => model.DefaultBank, new { data-validate-parameter: bankId })
This way I could just use the same action for all of the Bank entries like:
[HttpGet]
public JsonResult CheckValidBank(string bankId)
{
bool result = BankExists(bankId);
return Json(result, JsonRequestBehavior.AllowGet);
}
Possible?
For just such a situation, I wrote a RemoteReusableAttribute, which may be helpful to you. Here is a link to it: Custom remote Validation in MVC 3
Since MVC uses the default model binder for this, just like a normal action method. You could take a FormsCollection as your parameter and lookup the value. However, I personally would find it much easier to just use several parameters to the function, unless you start having dozens of different parameters.
You could also write a custom model binder, that would translate the passed parameter to a generic one.
Consider encapsulating the logic, "BankExists" in this case into a ValidationAttribute (Data Annotations Validator). This allows other scenarios as well.
Then use a wrapper ActionResult like the one below, which lets you pass in any validator.
[HttpGet]
public ActionResult CheckRefundBank(string refundBank)
{
var validation = BankExistsAttribute();
return new RemoteValidationResult(validation, defaultBank);
}
Here is the code for the ActionResult that works generically with Validators.
using System.ComponentModel.DataAnnotations;
using System.Web.Mvc;
public class RemoteValidationResult : ActionResult
{
public RemoteValidationResult(ValidationAttribute validation, object value)
{
this.Validation = validation;
this.Value = value;
}
public ValidationAttribute Validation { get; set; }
public object Value { get; set; }
public override void ExecuteResult(ControllerContext context)
{
var json = new JsonResult();
json.JsonRequestBehavior = JsonRequestBehavior.AllowGet;
if (Validation.IsValid(Value))
{
json.Data = true;
}
else
{
json.Data = Validation.FormatErrorMessage(Value.ToString());
}
json.ExecuteResult(context);
}
}
As an extra enhancement consider creating a Controller Extension method to dry up your return call even more.

MVC3 Return View By Default

Basically, I was wondering if anyone knows of a way that you can set up MVC3 in a way that it will first look for an action, and if none exists, it will automatically return the view at that location. Otherwise each time I make a page, I will have to rebuild it after adding the action.
It isn't something that's stopping the project from working nor is it an issue, it would just be a very nice thing to include in the code to help with speed of testing more than anything.
EDIT:
Just for clarity purposes, this is what I do every time I create a view that doesn't have any logic inside it:
public ActionResult ActionX()
{
return View();
}
Sometimes I will want some logic inside the action, but majority of the time for blank pages I will just want the above code.
I would like it if there was any way to always return the above code for every Controller/Action combination, UNLESS I have already made an action, then it should use the Action that I have specified.
Thanks,
Jake
Why not just create a single action for this. This will look for a view with the specified name and return a 404 if it doesn't exist.
[HttpGet]
public ActionResult Page(string page)
{
ViewEngineResult result = ViewEngines.Engines.FindView(ControllerContext, page, null);
if (result == null)
{
return HttpNotFound();
}
return View(page);
}
Then make your default route fall back to this:
routes.MapRoute("", "{page}", new { controller = "Home", action = "Page" });
So a request to http://yoursite.com/somepage will invoke Page("somepage")
I'm not altogether sure how useful this will be (or whether its really a good idea) but I guess if you have pages which are purely static content (but maybe use a layout or something so you can't use static html) it could be useful
This is how it could be done though anyway (as a base class, but it doesn't have to be)
public abstract class BaseController : Controller
{
public ActionResult Default()
{
return View();
}
protected override IActionInvoker CreateActionInvoker()
{
return new DefaultActionInvoker();
}
private class DefaultActionInvoker : ControllerActionInvoker
{
protected override ActionDescriptor FindAction(ControllerContext controllerContext, ControllerDescriptor controllerDescriptor, string actionName)
{
var actionDescriptor = base.FindAction(controllerContext, controllerDescriptor, actionName);
if (actionDescriptor == null)
actionDescriptor = base.FindAction(controllerContext, controllerDescriptor, "Default");
return actionDescriptor;
}
}
}

Resources