mvc 3 default route not found - asp.net-mvc-3

I have problems with default route for admin part.
I change folder structure from default, and now I have structure how at picture
for admin
for modules
I created two ViewEngine AdminViewEngine and CoreViewEngine
AdminViewEngine
public class AdminViewEngine : RazorViewEngine
{
public AdminViewEngine()
{
AreaMasterLocationFormats = new[] { "~/Admin/Views/Shared/admin.cshtml" };
AreaPartialViewLocationFormats = new[] { "~/Admin/Views/Shared/Partials/{0}.cshtml", "~/Admin/Views/{1}/Partials/{0}.cshtml", "~/Admin/Menu/{0}.cshtml" };
AreaViewLocationFormats = new[] { "~/Admin/Views/{1}/{0}.cshtml", "~/Admin/Menu/{0}.cshtml" };
PartialViewLocationFormats = AreaPartialViewLocationFormats;
ViewLocationFormats = AreaViewLocationFormats;
MasterLocationFormats = AreaMasterLocationFormats;
}
protected override IView CreatePartialView(ControllerContext controllerContext, string partialPath)
{
return new RazorView(controllerContext, partialPath, null, false, FileExtensions);
}
protected override IView CreateView(ControllerContext controllerContext, string viewPath, string masterPath)
{
if (string.IsNullOrEmpty(masterPath))
{
masterPath = AreaMasterLocationFormats[0];
}
var view = new RazorView(controllerContext, viewPath, masterPath, true, FileExtensions);
return view;
}
}
CoreViewEngine
public class CoreViewEngine : RazorViewEngine
{
internal static readonly string Theme = SiteSettings.Instance.Theme;
public CoreViewEngine()
{
string masterCatalog = string.Format("~/Themes/{0}/Views", Theme);
string masterPath = string.Format("{0}/master.cshtml", masterCatalog);
string partialThemesFolder = string.Format("~/Themes/{0}/Partials", Theme);
MasterLocationFormats = new[] { masterPath };
ViewLocationFormats = new[] { "~/Modules/Views/{1}/{0}.cshtml" };
PartialViewLocationFormats = new[]
{
"~/Blocks/{0}.cshtml", partialThemesFolder + "/{0}.cshtml", "~/Modules/Views/{1}/Partials/{0}.cshtml",
"~/Modules/Views/{1}/{0}.cshtml"
};
}
protected override IView CreatePartialView(ControllerContext controllerContext, string partialPath)
{
return new RazorView(controllerContext, partialPath, null, false, FileExtensions, ViewPageActivator);
}
protected override IView CreateView(ControllerContext controllerContext, string viewPath, string masterPath)
{
if (string.IsNullOrEmpty(masterPath))
{
string masterCatalog = string.Format("~/Themes/{0}/Views", Theme);
masterPath = string.Format("{0}/master.cshtml", masterCatalog);
}
var view = new RazorView(controllerContext, viewPath, masterPath, true, FileExtensions, ViewPageActivator);
return view;
}
}
And I have class for registered core and admin
public class ModulSetup : BaseModule
{
public override void Setup()
{
this.SetupViewEngine();
this.SetupControllers();
}
public override void SetupRoutes(RouteCollection routes)
{
routes.MapRoute("AdminRoot", "Admin", new { controller = "Site", action = "Index" });
routes.MapRoute(
"Admin",
"Admin/{controller}/{action}/{id}",
new { controller = "Site", action = "SiteSetting", id = UrlParameter.Optional });
routes.MapRoute(
"Default",
string.Empty,
new { controller = "News", action = "LastNews" });
}
protected void SetupViewEngine()
{
ViewEngines.Engines.Add(new CoreViewEngine());
ViewEngines.Engines.Add(new AdminViewEngine());
}
protected override void SetupControllers()
{
ControllerBuilder.Current.DefaultNamespaces.Add("SGN.Core.Admin.Controllers");
}
}
Admin workin. But if I try go to default admin page _http://sitename/Admin/ I get 404
I try different variants for registered routes. But not help. I don't know what I must to do

Related

Web Api Dependency Injection In Custom AuthorizeAttribute Via Unity Doesn't Work

I've asked this question and marked as duplicated but there is a problem in dependency injection in custom AuthorizeAttribute in my design. I've tried
several codes from some same answers. Everything is working correctly except attributes, _authenticationService is always being null. My code:
public class AuthenticateAttribute : AuthorizeAttribute
{
[Dependency]
private IAuthenticationService _authenticationService { get; set; }
public AuthenticateAttribute()
{
//_authenticationService is null here, even tried this code too:
//
}
Test 1
UnityActionFilterProvider
public class UnityActionFilterProvider : IFilterProvider
{
private readonly IUnityContainer container;
public UnityActionFilterProvider(IUnityContainer container)
{
this.container = container;
}
public IEnumerable<FilterInfo> GetFilters(HttpConfiguration configuration,
HttpActionDescriptor actionDescriptor)
{
foreach (IActionFilter actionFilter in container.ResolveAll<IActionFilter>())
{
// TODO: Determine correct FilterScope
yield return new FilterInfo(actionFilter, FilterScope.Global);
}
}
}
RegisterComponents
public static void RegisterComponents()
{
var container = new UnityContainer();
container.RegisterType<IAuthenticationService, AuthenticationService>(new HierarchicalLifetimeManager());
GlobalConfiguration.Configuration.DependencyResolver = new UnityDependencyResolver(container);
var providers = GlobalConfiguration.Configuration.Services.GetFilterProviders().ToList();
GlobalConfiguration.Configuration.Services.Add(typeof(IFilterProvider), new UnityActionFilterProvider(container));
var defaultprovider = providers.First(p => p is ActionDescriptorFilterProvider);
GlobalConfiguration.Configuration.Services.Remove(typeof(IFilterProvider), defaultprovider);
}
Global.asax
public class WebApiApplication : System.Web.HttpApplication
{
protected void Application_Start()
{
UnityConfig.RegisterComponents();
GlobalConfiguration.Configure(WebApiConfig.Register);
}
}
Test 2
*UnityFilterAttributeFilterProvider *
public class UnityFilterAttributeFilterProvider : FilterAttributeFilterProvider
{
private IUnityContainer _container;
public UnityFilterAttributeFilterProvider(IUnityContainer container)
{
_container = container;
}
protected override IEnumerable<FilterAttribute> GetControllerAttributes(
ControllerContext controllerContext,
ActionDescriptor actionDescriptor)
{
var attributes = base.GetControllerAttributes(controllerContext,
actionDescriptor);
foreach (var attribute in attributes)
{
_container.BuildUp(attribute.GetType(), attribute);
}
return attributes;
}
protected override IEnumerable<FilterAttribute> GetActionAttributes(
ControllerContext controllerContext,
ActionDescriptor actionDescriptor)
{
var attributes = base.GetActionAttributes(controllerContext,
actionDescriptor);
foreach (var attribute in attributes)
{
_container.BuildUp(attribute.GetType(), attribute);
}
return attributes;
}
}
RegisterComponents
public static void RegisterComponents()
{
var container = new UnityContainer();
container.RegisterType<IAuthenticationService, AuthenticationService>(new HierarchicalLifetimeManager());
GlobalConfiguration.Configuration.DependencyResolver = new UnityDependencyResolver(container);
var oldProvider = System.Web.Mvc.FilterProviders.Providers.Single(f => f is System.Web.Mvc.FilterAttributeFilterProvider);
System.Web.Mvc.FilterProviders.Providers.Remove(oldProvider);
var provider = new UnityFilterAttributeFilterProvider(container);
container.RegisterInstance<UnityFilterAttributeFilterProvider>(provider);
System.Web.Mvc.FilterProviders.Providers.Add(provider);
}
Global.asax
public class WebApiApplication : System.Web.HttpApplication
{
protected void Application_Start()
{
UnityConfig.RegisterComponents();
GlobalConfiguration.Configure(WebApiConfig.Register);
}
}

Custom Filter Attribute or call in Action Method - Earlier in Request Pipeline but Performance?

I have a HomeController with about 8 or 9 Action Methods.
About 7 of these methods require a check to see if the User has a special setting or not to see if they are allowed to access these Methods and related Views.
If they are not they are redirected back to a Common Action Method and View.
public class HomeController : Controller
{
public ActionResult Index() {
UserManager um = new UserManager();
um.Punter p = um.GetPunter(User.Identity.Name);
return View(p);
}
public ActionResult PunterList() {
UserManager um = new UserManager();
um.Punter p = um.GetPunter(User.Identity.Name);
if (p.isPunter) {
return RedirectToAction("Index", "Home");
} else {
return View(p);
}
}
}
The check in 'PunterList' is done in other Action Methods, I was thinking about creating a FilterAttribute to do this check. As per the following:
public class NoPunterAttribute : FilterAttribute, IActionFilter {
public void OnActionExecuting(ActionExecutingContext filterContext) {
UserManager um = new UserManager();
um.Punter p = um.GetPunter(User.Identity.Name);
if (p.isPunter) {
filterContext.Result = new RedirectToRouteResult(new RouteValueDictionary { { "controller", "Home" }, { "action", "Index" } });
}
}
public void OnActionExecuted(ActionExecutedContext filterContext) { }
}
then put this attribute on the Action method this type of user cannot access.
[NoPunter]
public ActionResult PunterList() {
UserManager um = new UserManager();
um.Punter p = um.GetPunter(User.Identity.Name);
return View(p);
}
this puts this code in 1 place, However the UserManager.GetPunter is called twice if the User.isPunter=false. Perhaps this is not such a good idea for Performance or Memory conservation of the MVC web application.
The benefit is does the check earlier in the Request pipeline, but perhaps a method called inside of the action method would mean .GetPunter would be called only once, but further along the Request pipeline. Not sure about this, kind of split on earlier vs Performance/Memory issues.
Any suggestions or ideas would be interesting to hear. Presumably it would depend on what is done inside UserManager.GetPunter. There is some caching inside this call but it does requery the cache.
You could write a custom authorization attribute which will inject the Punter as a parameter of your action:
public class HomeController : Controller
{
public ActionResult Index()
{
UserManager um = new UserManager();
um.Punter p = um.GetPunter(User.Identity.Name);
return View(p);
}
[NoPunterAuthorize]
public ActionResult PunterList(Punter punter)
{
return View(punter);
}
}
and the custom authorization attribute:
public class NoPunterAuthorize: AuthorizeAttribute
{
protected override bool AuthorizeCore(HttpContextBase httpContext)
{
var authorized = base.AuthorizeCore(httpContext);
if (!authorized)
{
return false;
}
var um = new UserManager();
var p = um.GetPunter(httpContext.User.Identity.Name);
var routeData = httpContext.Request.RequestContext.RouteData;
routeData.Values["punter"] = p;
return !p.IsPunter;
}
protected override void HandleUnauthorizedRequest(AuthorizationContext filterContext)
{
filterContext.Result = new RedirectToRouteResult(
new RouteValueDictionary
{
{ "controller", "Home" },
{ "action", "Index" }
}
);
}
}

ASP.NET MVC 3 RedirectToRouteResult

RedirectToRouteResult does not redirect to the specified controller-action pair.
public class CustAuthorizeAttribute : AuthorizeAttribute, IAuthorizationFilter
{
...
public override void OnAuthorization(AuthorizationContext filterContext)
{
base.OnAuthorization(filterContext);
...
if (...) {
var routeDictionary = new RouteValueDictionary { { "action", "Forbidden" }, { "controller", "Error" } };
filterContext.Result = new RedirectToRouteResult(routeDictionary);
}
}
}
Please, help.
Perhaps you could try something like:
UrlHelper urlHelper = new UrlHelper(filterContext.HttpContext.Request.RequestContext);
filterContext.Result = urlHelper.Action("Forbidden" , "Error");

ASP.NET MVC 3 Localization using route and view

I have searched and tried many localization approaches but all is not exactly what I want. Basically, I want to have my url like this
www.myweb.com <== default language (which is English)
www.myweb.com/promotion
www.myweb.com/th/promotion <== local language (Thai)
www.myweb.com/cn/promotion <== local language (Chinese)
Then I want these url to map with different View structure as below
/Views
/_Localization
/cn
/Home
/About.cshtml
/Index.cshtml
/Shared
/_Layout.cshtml
/Error.cshtml
/th
/Home
/About.cshtml
/Shared
/Home
/About.cshtml
/Index.cshtml
/Shared
/_Layout.cshtml
/_LogOnPartial.cshtml
/Error.cshtml
_ViewStart.cshtml
Web.config
As you can seen, Thai doesn't have it own Index.cshtml, _Layout.cshtml and Error.cshtml. So, I would like this to fallback to use the default instead. But chinese will use it own.
I have tried to MapRoute like this
routes.MapRoute(
"DefaultLocal",
"{lang}/{controller}/{action}/{id}",
new { lang = "th", controller = "Home", action = "Index", id = UrlParameter.Optional }
);
routes.MapRoute(
"Default",
"{controller}/{action}/{id}",
new { controller = "Home", action = "Index", id = UrlParameter.Optional }
);
but I don't know how to point to different View structure. And in this example, Brian Reiter, it use Cookie not url.
So, how can I achieve this. note that I use RazorViewEngine.
Thank you for any help and thought.
Due to large amount of code needed i will only illustrate an idea of how it could be done.
You can subclass from RazorViewEngine like this:
public class I18NRazorViewEngine : RazorViewEngine
{
public I18NRazorViewEngine() : this(null)
{ }
protected string[] I18NAreaViewLocationFormats;
protected string[] I18NAreaMasterLocationFormats;
protected string[] I18NAreaPartialViewLocationFormats;
protected string[] I18NViewLocationFormats;
protected string[] I18NMasterLocationFormats;
protected string[] I18NPartialViewLocationFormats;
public I18NRazorViewEngine(IViewPageActivator viewPageActivator)
: base(viewPageActivator)
{
this.I18NAreaViewLocationFormats = new string[]
{
"~/Areas/{3}/{2}/Views/{1}/{0}.cshtml",
"~/Areas/{3}/{2}/Views/{1}/{0}.vbhtml",
"~/Areas/{3}/{2}/Views/Shared/{0}.cshtml",
"~/Areas/{3}/{2}/Views/Shared/{0}.vbhtml"
};
this.I18NAreaMasterLocationFormats = new string[]
{
"~/Areas/{3}/{2}/Views/{1}/{0}.cshtml",
"~/Areas/{3}/{2}/Views/{1}/{0}.vbhtml",
"~/Areas/{3}/{2}/Views/Shared/{0}.cshtml",
"~/Areas/{3}/{2}/Views/Shared/{0}.vbhtml"
};
this.I18NAreaPartialViewLocationFormats = new string[]
{
"~/Areas/{3}/{2}/Views/{1}/{0}.cshtml",
"~/Areas/{3}/{2}/Views/{1}/{0}.vbhtml",
"~/Areas/{3}/{2}/Views/Shared/{0}.cshtml",
"~/Areas/{3}/{2}/Views/Shared/{0}.vbhtml"
};
this.I18NViewLocationFormats = new string[]
{
"~/Views/{2}/{1}/{0}.cshtml",
"~/Views/{2}/{1}/{0}.vbhtml",
"~/Views/{2}/Shared/{0}.cshtml",
"~/Views/{2}/Shared/{0}.vbhtml"
};
this.I18NMasterLocationFormats = new string[]
{
"~/Views/{2}/{1}/{0}.cshtml",
"~/Views/{2}/{1}/{0}.vbhtml",
"~/Views/{2}/Shared/{0}.cshtml",
"~/Views/{2}/Shared/{0}.vbhtml"
};
this.I18NPartialViewLocationFormats = new string[]
{
"~/Views/{2}/{1}/{0}.cshtml",
"~/Views/{2}/{1}/{0}.vbhtml",
"~/Views/{2}/Shared/{0}.cshtml",
"~/Views/{2}/Shared/{0}.vbhtml"
};
}
public override ViewEngineResult FindView(ControllerContext controllerContext, string viewName, string masterName, bool useCache)
{
var langValue = controllerContext.Controller.ValueProvider.GetValue("lang");
if (langValue == null || String.IsNullOrEmpty(langValue.AttemptedValue))
return base.FindView(controllerContext, viewName, masterName, useCache);
//Code here
}
public override ViewEngineResult FindPartialView(ControllerContext controllerContext, string partialViewName, bool useCache)
{
var langValue = controllerContext.Controller.ValueProvider.GetValue("lang");
if (langValue == null || String.IsNullOrEmpty(langValue.AttemptedValue))
return base.FindPartialView(controllerContext, partialViewName, useCache);
//Code here
}
}
The next what you should do is to look inside VirtualPathProviderViewEngine on FindView and FindPartialView inplementations. The reflected code is like this:
public virtual ViewEngineResult FindPartialView(ControllerContext controllerContext, string partialViewName, bool useCache)
{
if (controllerContext == null)
{
throw new ArgumentNullException("controllerContext");
}
if (string.IsNullOrEmpty(partialViewName))
{
throw new ArgumentException(MvcResources.Common_NullOrEmpty, "partialViewName");
}
string requiredString = controllerContext.RouteData.GetRequiredString("controller");
string[] searchedLocations;
string path = this.GetPath(controllerContext, this.PartialViewLocationFormats, this.AreaPartialViewLocationFormats, "PartialViewLocationFormats", partialViewName, requiredString, "Partial", useCache, out searchedLocations);
if (string.IsNullOrEmpty(path))
{
return new ViewEngineResult(searchedLocations);
}
return new ViewEngineResult(this.CreatePartialView(controllerContext, path), this);
}
and
public virtual ViewEngineResult FindView(ControllerContext controllerContext, string viewName, string masterName, bool useCache)
{
if (controllerContext == null)
{
throw new ArgumentNullException("controllerContext");
}
if (string.IsNullOrEmpty(viewName))
{
throw new ArgumentException(MvcResources.Common_NullOrEmpty, "viewName");
}
string requiredString = controllerContext.RouteData.GetRequiredString("controller");
string[] first;
string path = this.GetPath(controllerContext, this.ViewLocationFormats, this.AreaViewLocationFormats, "ViewLocationFormats", viewName, requiredString, "View", useCache, out first);
string[] second;
string path2 = this.GetPath(controllerContext, this.MasterLocationFormats, this.AreaMasterLocationFormats, "MasterLocationFormats", masterName, requiredString, "Master", useCache, out second);
if (string.IsNullOrEmpty(path) || (string.IsNullOrEmpty(path2) && !string.IsNullOrEmpty(masterName)))
{
return new ViewEngineResult(first.Union(second));
}
return new ViewEngineResult(this.CreateView(controllerContext, path, path2), this);
}
both methods rely on private GetPath method:
private string GetPath(ControllerContext controllerContext, string[] locations, string[] areaLocations, string locationsPropertyName, string name, string controllerName, string cacheKeyPrefix, bool useCache, out string[] searchedLocations)
{
searchedLocations = VirtualPathProviderViewEngine._emptyLocations;
if (string.IsNullOrEmpty(name))
{
return string.Empty;
}
string areaName = AreaHelpers.GetAreaName(controllerContext.RouteData);
List<VirtualPathProviderViewEngine.ViewLocation> viewLocations = VirtualPathProviderViewEngine.GetViewLocations(locations, (!string.IsNullOrEmpty(areaName)) ? areaLocations : null);
if (viewLocations.Count == 0)
{
throw new InvalidOperationException(string.Format(CultureInfo.CurrentCulture, MvcResources.Common_PropertyCannotBeNullOrEmpty, new object[]
{
locationsPropertyName
}));
}
bool flag = VirtualPathProviderViewEngine.IsSpecificPath(name);
string text = this.CreateCacheKey(cacheKeyPrefix, name, flag ? string.Empty : controllerName, areaName);
if (useCache)
{
return this.ViewLocationCache.GetViewLocation(controllerContext.HttpContext, text);
}
if (!flag)
{
return this.GetPathFromGeneralName(controllerContext, viewLocations, name, controllerName, areaName, text, ref searchedLocations);
}
return this.GetPathFromSpecificName(controllerContext, name, text, ref searchedLocations);
}
What you should do is to reimplement it. Most of the code you can reuse, but you should create your own method instead of VirtualPathProviderViewEngine.GetViewLocations. Here its reflected code:
private static List<VirtualPathProviderViewEngine.ViewLocation> GetViewLocations(string[] viewLocationFormats, string[] areaViewLocationFormats)
{
List<VirtualPathProviderViewEngine.ViewLocation> list = new List<VirtualPathProviderViewEngine.ViewLocation>();
if (areaViewLocationFormats != null)
{
for (int i = 0; i < areaViewLocationFormats.Length; i++)
{
string virtualPathFormatString = areaViewLocationFormats[i];
list.Add(new VirtualPathProviderViewEngine.AreaAwareViewLocation(virtualPathFormatString));
}
}
if (viewLocationFormats != null)
{
for (int j = 0; j < viewLocationFormats.Length; j++)
{
string virtualPathFormatString2 = viewLocationFormats[j];
list.Add(new VirtualPathProviderViewEngine.ViewLocation(virtualPathFormatString2));
}
}
return list;
}
You can also reuse most of the code but instead of VirtualPathProviderViewEngine.ViewLocation and VirtualPathProviderViewEngine.AreaAwareViewLocation you should use your own classes. They could be like this:
class ViewLocation
{
protected string _virtualPathFormatString;
public ViewLocation(string virtualPathFormatString)
{
this._virtualPathFormatString = virtualPathFormatString;
}
public virtual string Format(string viewName, string controllerName, string areaName, string lang)
{
return string.Format(CultureInfo.InvariantCulture, this._virtualPathFormatString, new object[]
{
viewName,
controllerName,
lang
});
}
}
and:
class AreaAwareViewLocation : VirtualPathProviderViewEngine.ViewLocation
{
public AreaAwareViewLocation(string virtualPathFormatString) : base(virtualPathFormatString)
{
}
public override string Format(string viewName, string controllerName, string areaName, string lang)
{
return string.Format(CultureInfo.InvariantCulture, this._virtualPathFormatString, new object[]
{
viewName,
controllerName,
areaName,
lang
});
}
}
and then when you will call Format methods you should pass langValue.AttemptedValue (it is from scope of FindView and FindPartialView in the first code block) to lang parameter. Normally it's called in VirtualPathProviderViewEngine.GetPathFromGeneralName.
The main advice is to use ILSpy or another disassembler to explore the code of System.Web.Mvc (or even better - download its sources). The goal is to reimplement FindView and FindPartialView. The rest code provided is to illustrate how it's already done in mvc framework.
It's also important to to seek through arrays declared in our new view engine instead of those without I18N prefix which are already there and used by default classes
Hope it will help despite answer is indirect. You can ask additional questions if you will face any difficulties.
P.S. Don't foreget to register you view engine in global.asax.cs after it will be developed.
protected virtual void Application_Start()
{
ViewEngines.Engines.Clear();
ViewEngines.Engines.Add(new I18NRazorViewEngine());
}

How to unit test modelbinder with ModelMetadata

How do I unit test a custom ModelBinder?
Here's the code.
public class MagicBinder : DefaultModelBinder
{
public override object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
{
var boundModelObject = base.BindModel(controllerContext, bindingContext);
var properties = bindingContext.ModelType.GetProperties().Where(a => a.CanWrite);
foreach (var propertyInfo in properties)
{
object outValue = null;
bindingContext.TryGetValue(propertyInfo.Name, propertyInfo.DeclaringType, out outValue);
propertyInfo.SetValue(boundModelObject, outValue, null);
}
return boundModelObject;
}
}
And here is the test script.
[TestMethod]
public void TestFooBinding()
{
var dict = new ValueProviderDictionary(null)
{
{"Number", new ValueProviderResult("2", "2", null)},
{"Test", new ValueProviderResult("12", "12", null)},
};
var bindingContext = new ModelBindingContext() { ModelName = "foo", ValueProvider = dict};
var target = new MagicBinder();
Foo result = (Foo)target.BindModel(null, bindingContext);
}
public class Foo
{
public int Number { get; set; }
public int Test { get; set; }
}
Problem? In the MagicBinder, bindingContext.Model is null. If I try set it with
bindingContext.Model = new Foo(). I get an exception saying it is deprecated, and I should set the ModelMetadata.
So how do I construct a ModelMetadata? It can't even be mocked.
NOTE: This answer is for ASP.NET on .NET Framework and might be outdated.
Try like this:
[TestMethod]
public void TestFooBinding()
{
// arrange
var formCollection = new NameValueCollection
{
{ "Number", "2" },
{ "Test", "12" },
};
var valueProvider = new NameValueCollectionValueProvider(formCollection, null);
var metadata = ModelMetadataProviders.Current.GetMetadataForType(null, typeof(Foo));
var bindingContext = new ModelBindingContext
{
ModelName = "",
ValueProvider = valueProvider,
ModelMetadata = metadata
};
var controllerContext = new ControllerContext();
var sut = new MagicBinder();
// act
Foo actual = (Foo)sut.BindModel(controllerContext, bindingContext);
// assert
// TODO:
}
Incase any of you need this to work for web-api you can use this method which will test's Get Requests, you get the benefit of using the built in provider:
Which will populate the values as the would come in from the web, instead of getting bizarre side effects of creating values that the provider may potentially never return Null etc.
using System;
using System.Globalization;
using System.Net.Http;
using System.Web.Http.Controllers;
using System.Web.Http.Metadata.Providers;
using System.Web.Http.ModelBinding;
using System.Web.Http.ValueProviders.Providers;
namespace Apps.API.Web.Tests
{
public class ModelBinderTestRule
{
//This URL is just a place holder for prefixing the query string
public const string MOCK_URL = "http://localhost:8088/";
public TModel BindModelFromGet<TBinder, TModel>(string modelName, string queryString, TBinder binder)
where TBinder : IModelBinder
{
var httpControllerContext = new HttpControllerContext();
httpControllerContext.Request = new HttpRequestMessage(HttpMethod.Get, MOCK_URL + queryString);
var bindingContext = new ModelBindingContext();
var dataProvider = new DataAnnotationsModelMetadataProvider();
var modelMetadata = dataProvider.GetMetadataForType(null, typeof(TModel));
var httpActionContext = new HttpActionContext();
httpActionContext.ControllerContext = httpControllerContext;
var provider = new QueryStringValueProvider(httpActionContext, CultureInfo.InvariantCulture);
bindingContext.ModelMetadata = modelMetadata;
bindingContext.ValueProvider = provider;
bindingContext.ModelName = modelName;
if (binder.BindModel(httpActionContext, bindingContext))
{
return (TModel)bindingContext.Model;
}
throw new Exception("Model was not bindable");
}
}
}

Resources