MVC3 - UpdateModel... how to change incoming data? - asp.net-mvc-3

UpdateModel fails because arcm.Notes was null coming into this method and I want it to be an empty string.
Maybe I need to refresh the ValueProvider (?) after setting Notes to "", so I can use UpdateModel.
public ActionResult Edit1(Guid id, ActivityResponseConsumerMobile arcm) {
if (arcm.Notes == null)
arcm.Notes = "";
if (!ModelState.IsValid) {
SetupDropDowns();
return View(arcm);
}
ActivityResponseConsumerMobile arcmDb = uow.ActivityResponseConsumerMobiles.Single(a => a.Id == id);
try {
UpdateModel(arcmDb, null, null, new[] { "Id" });

This is the default behavior I believe since MVC2.
note one workaround here:
http://brianreiter.org/2010/09/16/asp-net-mvc-2-0-undocumented-model-string-property-breaking-change/
public sealed class EmptyStringModelBinder : DefaultModelBinder
{
public override object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
{
bindingContext.ModelMetadata.ConvertEmptyStringToNull = false;
return base.BindModel(controllerContext, bindingContext);
}
}
And register the binder
protected void Application_Start()
{
ModelBinders.Binders.DefaultBinder = new EmptyStringModelBinder();
RegisterRoutes( RouteTable.Routes );
}

Related

Is DefaultModelBinder available for .net core mvc to encrypt and decrypt action parameters?

I want a customize modelbinder for .net core mvc action parameters, but I not sure DefaultModelBinder is available in .net core. this is .net standard example for customize modelbinder.
public class EncryptDataBinder : DefaultModelBinder
{
public override object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
{
object result = 0;
if (bindingContext.ModelType == typeof(int))
{
var valueProviderResult = bindingContext.ValueProvider.GetValue(bindingContext.ModelName);
if (valueProviderResult != null)
{
// decryption code
}
}
return base.BindModel(controllerContext, bindingContext);
}
}
In ASP.NET Core, you need implements IModelBinder.
A simple demo below about custom model binder to change the bound Id:
Model
public class Author
{
[ModelBinder(BinderType = typeof(EncryptDataBinder))]
public int Id { get; set; }
public string Name { get; set; }
}
Custom IModelBinder
public class EncryptDataBinder : IModelBinder
{
public Task BindModelAsync(ModelBindingContext bindingContext)
{
if (bindingContext == null)
{
throw new ArgumentNullException(nameof(bindingContext));
}
if (bindingContext.ModelType == typeof(int))
{
// Try to fetch the value of the argument by name
var valueProviderResult = bindingContext.ValueProvider.GetValue(bindingContext.ModelName);
if (valueProviderResult != null)
{
int id = 3;
//bind the new id....
bindingContext.Result = ModelBindingResult.Success(id);
return Task.CompletedTask;
}
}
return Task.CompletedTask;
}
}
Controller:
[HttpPost]
public IActionResult Post([FromForm]Author model)
{
return Ok(model);
}

ModelState to check all parameters in Web Api

This is my action the ModelState checks only the bookId parameter. The other one even if it is null, no error is raised.
Is there any way to make it check the ModelState of all parameters?
[HttpPut]
[Route("{bookId}")]
public IHttpActionResult Edit([FromBody] EditBookBindingModel model, int bookId)
{
if (!this.service.ExistsBook(bookId))
{
return this.NotFound();
}
if (!this.ModelState.IsValid)
{
return this.StatusCode(HttpStatusCode.BadRequest);
}
this.service.EditBook(bookId, model);
return this.Ok();
}
You could define an ActionFilterAttribute that protects you from null arguments:
public class CheckModelForNullAttribute : ActionFilterAttribute
{
public override void OnActionExecuting(HttpActionContext actionContext)
{
if (actionContext.ActionArguments.ContainsValue(null))
actionContext.Response = actionContext.Request.CreateErrorResponse(HttpStatusCode.BadRequest, "The argument cannot be null");
}
}
Then use this:
[HttpPut]
[Route("{bookId}")]
[CheckModelForNull]
public IHttpActionResult Edit([FromBody] EditBookBindingModel model, int bookId)
{
// model canĀ“t be null now
...
I wrote a custom filter so DataAnnotations works with parameters also.
Here is the filter.
public class ModelValidationFilter : FilterAttribute, IActionFilter, IFilter
{
public Task<HttpResponseMessage> ExecuteActionFilterAsync(HttpActionContext actionContext,
CancellationToken cancellationToken, Func<Task<HttpResponseMessage>> continuation)
{
var parameters = actionContext.ActionDescriptor.GetParameters();
if (parameters.Any())
{
var validationParams = parameters.Where(x => x.GetCustomAttributes<ValidationAttribute>().Any());
if (validationParams.Any())
{
foreach (var item in validationParams)
{
var val = actionContext.ActionArguments[item.ParameterName];
foreach (var attr in item.GetCustomAttributes<ValidationAttribute>())
{
if (!attr.IsValid(val))
{
actionContext.ModelState.AddModelError(item.ParameterName, attr.FormatErrorMessage(item.ParameterName));
}
}
}
}
if (!actionContext.ModelState.IsValid)
{
return Task.FromResult(actionContext.Request.CreateErrorResponse(HttpStatusCode.BadRequest, actionContext.ModelState));
}
}
return continuation();
}
}
Usage (I have'nt tested completely.):
add it to global filters.
config.Filters.Add(new ModelValidationFilter());
public Student Post([Required] addStudentDTO)
{
//Your logic
}
public Student Patch([FromBody,Required] addStudentDTO, [Required,EmailAddress]string emailAddress])
{
//Your logic
}

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());
}

Using JSON.NET as the default JSON serializer in ASP.NET MVC 3 - is it possible?

Is it possible to use JSON.NET as default JSON serializer in ASP.NET MVC 3?
According to my research, it seems that the only way to accomplish this is to extend ActionResult as JsonResult in MVC3 is not virtual...
I hoped that with ASP.NET MVC 3 that there would be a way to specify a pluggable provider for serializing to JSON.
Thoughts?
I believe the best way to do it, is - as described in your links - to extend ActionResult or extend JsonResult directly.
As for the method JsonResult that is not virtual on the controller that's not true, just choose the right overload. This works well:
protected override JsonResult Json(object data, string contentType, Encoding contentEncoding)
EDIT 1: A JsonResult extension...
public class JsonNetResult : JsonResult
{
public override void ExecuteResult(ControllerContext context)
{
if (context == null)
throw new ArgumentNullException("context");
var response = context.HttpContext.Response;
response.ContentType = !String.IsNullOrEmpty(ContentType)
? ContentType
: "application/json";
if (ContentEncoding != null)
response.ContentEncoding = ContentEncoding;
// If you need special handling, you can call another form of SerializeObject below
var serializedObject = JsonConvert.SerializeObject(Data, Formatting.Indented);
response.Write(serializedObject);
}
EDIT 2: I removed the check for Data being null as per the suggestions below. That should make newer versions of JQuery happy and seems like the sane thing to do, as the response can then be unconditionally deserialized. Be aware though, that this is not the default behavior for JSON responses from ASP.NET MVC, which rather responds with an empty string, when there's no data.
I implemented this without the need of a base controller or injection.
I used action filters to replace the JsonResult with a JsonNetResult.
public class JsonHandlerAttribute : ActionFilterAttribute
{
public override void OnActionExecuted(ActionExecutedContext filterContext)
{
var jsonResult = filterContext.Result as JsonResult;
if (jsonResult != null)
{
filterContext.Result = new JsonNetResult
{
ContentEncoding = jsonResult.ContentEncoding,
ContentType = jsonResult.ContentType,
Data = jsonResult.Data,
JsonRequestBehavior = jsonResult.JsonRequestBehavior
};
}
base.OnActionExecuted(filterContext);
}
}
In the Global.asax.cs Application_Start() you would need to add:
GlobalFilters.Filters.Add(new JsonHandlerAttribute());
For completion's sake, here is my JsonNetResult extention class that I picked up from somewhere else and that I modified slightly to get correct steaming support:
public class JsonNetResult : JsonResult
{
public JsonNetResult()
{
Settings = new JsonSerializerSettings
{
ReferenceLoopHandling = ReferenceLoopHandling.Error
};
}
public JsonSerializerSettings Settings { get; private set; }
public override void ExecuteResult(ControllerContext context)
{
if (context == null)
throw new ArgumentNullException("context");
if (this.JsonRequestBehavior == JsonRequestBehavior.DenyGet && string.Equals(context.HttpContext.Request.HttpMethod, "GET", StringComparison.OrdinalIgnoreCase))
throw new InvalidOperationException("JSON GET is not allowed");
HttpResponseBase response = context.HttpContext.Response;
response.ContentType = string.IsNullOrEmpty(this.ContentType) ? "application/json" : this.ContentType;
if (this.ContentEncoding != null)
response.ContentEncoding = this.ContentEncoding;
if (this.Data == null)
return;
var scriptSerializer = JsonSerializer.Create(this.Settings);
scriptSerializer.Serialize(response.Output, this.Data);
}
}
Use Newtonsoft's JSON converter:
public ActionResult DoSomething()
{
dynamic cResponse = new ExpandoObject();
cResponse.Property1 = "value1";
cResponse.Property2 = "value2";
return Content(JsonConvert.SerializeObject(cResponse), "application/json");
}
I know this is well after the question has been answered, but I'm using a different approach as I am using dependency injection to instantiate my controllers.
I have replaced the IActionInvoker ( by injecting the controller's ControllerActionInvoker Property ) with a version that overrides the InvokeActionMethod method.
This means no change to controller inheritance and it can be easily removed when I upgrade to MVC4 by altering the DI container's registration for ALL controllers
public class JsonNetActionInvoker : ControllerActionInvoker
{
protected override ActionResult InvokeActionMethod(ControllerContext controllerContext, ActionDescriptor actionDescriptor, IDictionary<string, object> parameters)
{
ActionResult invokeActionMethod = base.InvokeActionMethod(controllerContext, actionDescriptor, parameters);
if ( invokeActionMethod.GetType() == typeof(JsonResult) )
{
return new JsonNetResult(invokeActionMethod as JsonResult);
}
return invokeActionMethod;
}
private class JsonNetResult : JsonResult
{
public JsonNetResult()
{
this.ContentType = "application/json";
}
public JsonNetResult( JsonResult existing )
{
this.ContentEncoding = existing.ContentEncoding;
this.ContentType = !string.IsNullOrWhiteSpace(existing.ContentType) ? existing.ContentType : "application/json";
this.Data = existing.Data;
this.JsonRequestBehavior = existing.JsonRequestBehavior;
}
public override void ExecuteResult(ControllerContext context)
{
if (context == null)
{
throw new ArgumentNullException("context");
}
if ((this.JsonRequestBehavior == JsonRequestBehavior.DenyGet) && string.Equals(context.HttpContext.Request.HttpMethod, "GET", StringComparison.OrdinalIgnoreCase))
{
base.ExecuteResult(context); // Delegate back to allow the default exception to be thrown
}
HttpResponseBase response = context.HttpContext.Response;
response.ContentType = this.ContentType;
if (this.ContentEncoding != null)
{
response.ContentEncoding = this.ContentEncoding;
}
if (this.Data != null)
{
// Replace with your favourite serializer.
new Newtonsoft.Json.JsonSerializer().Serialize( response.Output, this.Data );
}
}
}
}
--- EDIT - Updated to show container registration for controllers. I'm using Unity here.
private void RegisterAllControllers(List<Type> exportedTypes)
{
this.rootContainer.RegisterType<IActionInvoker, JsonNetActionInvoker>();
Func<Type, bool> isIController = typeof(IController).IsAssignableFrom;
Func<Type, bool> isIHttpController = typeof(IHttpController).IsAssignableFrom;
foreach (Type controllerType in exportedTypes.Where(isIController))
{
this.rootContainer.RegisterType(
typeof(IController),
controllerType,
controllerType.Name.Replace("Controller", string.Empty),
new InjectionProperty("ActionInvoker")
);
}
foreach (Type controllerType in exportedTypes.Where(isIHttpController))
{
this.rootContainer.RegisterType(typeof(IHttpController), controllerType, controllerType.Name);
}
}
public class UnityControllerFactory : System.Web.Mvc.IControllerFactory, System.Web.Http.Dispatcher.IHttpControllerActivator
{
readonly IUnityContainer container;
public UnityControllerFactory(IUnityContainer container)
{
this.container = container;
}
IController System.Web.Mvc.IControllerFactory.CreateController(System.Web.Routing.RequestContext requestContext, string controllerName)
{
return this.container.Resolve<IController>(controllerName);
}
SessionStateBehavior System.Web.Mvc.IControllerFactory.GetControllerSessionBehavior(RequestContext requestContext, string controllerName)
{
return SessionStateBehavior.Required;
}
void System.Web.Mvc.IControllerFactory.ReleaseController(IController controller)
{
}
IHttpController IHttpControllerActivator.Create(HttpRequestMessage request, HttpControllerDescriptor controllerDescriptor, Type controllerType)
{
return this.container.Resolve<IHttpController>(controllerType.Name);
}
}
Expanding on the answer from https://stackoverflow.com/users/183056/sami-beyoglu, if you set the Content type, then jQuery will be able to convert the returned data into an object for you.
public ActionResult DoSomething()
{
dynamic cResponse = new ExpandoObject();
cResponse.Property1 = "value1";
cResponse.Property2 = "value2";
return Content(JsonConvert.SerializeObject(cResponse), "application/json");
}
My Post may help someone.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Web;
using System.Web.Mvc;
namespace MultipleSubmit.Service
{
public abstract class BaseController : Controller
{
protected override JsonResult Json(object data, string contentType,
Encoding contentEncoding, JsonRequestBehavior behavior)
{
return new JsonNetResult
{
Data = data,
ContentType = contentType,
ContentEncoding = contentEncoding,
JsonRequestBehavior = behavior
};
}
}
}
using Newtonsoft.Json;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Web;
using System.Web.Mvc;
namespace MultipleSubmit.Service
{
public class JsonNetResult : JsonResult
{
public JsonNetResult()
{
Settings = new JsonSerializerSettings
{
ReferenceLoopHandling = ReferenceLoopHandling.Error
};
}
public JsonSerializerSettings Settings { get; private set; }
public override void ExecuteResult(ControllerContext context)
{
if (context == null)
throw new ArgumentNullException("context");
if (this.JsonRequestBehavior == JsonRequestBehavior.DenyGet && string.Equals
(context.HttpContext.Request.HttpMethod, "GET", StringComparison.OrdinalIgnoreCase))
throw new InvalidOperationException("JSON GET is not allowed");
HttpResponseBase response = context.HttpContext.Response;
response.ContentType = string.IsNullOrEmpty(this.ContentType) ?
"application/json" : this.ContentType;
if (this.ContentEncoding != null)
response.ContentEncoding = this.ContentEncoding;
if (this.Data == null)
return;
var scriptSerializer = JsonSerializer.Create(this.Settings);
using (var sw = new StringWriter())
{
scriptSerializer.Serialize(sw, this.Data);
response.Write(sw.ToString());
}
}
}
}
public class MultipleSubmitController : BaseController
{
public JsonResult Index()
{
var data = obj1; // obj1 contains the Json data
return Json(data, JsonRequestBehavior.AllowGet);
}
}
I made a version that makes web service actions type-safe and simple. You use it like this:
public JsonResult<MyDataContract> MyAction()
{
return new MyDataContract();
}
The class:
public class JsonResult<T> : JsonResult
{
public JsonResult(T data)
{
Data = data;
JsonRequestBehavior = JsonRequestBehavior.AllowGet;
}
public override void ExecuteResult(ControllerContext context)
{
// Use Json.Net rather than the default JavaScriptSerializer because it's faster and better
if (context == null)
throw new ArgumentNullException("context");
var response = context.HttpContext.Response;
response.ContentType = !String.IsNullOrEmpty(ContentType)
? ContentType
: "application/json";
if (ContentEncoding != null)
response.ContentEncoding = ContentEncoding;
var serializedObject = JsonConvert.SerializeObject(Data, Formatting.Indented);
response.Write(serializedObject);
}
public static implicit operator JsonResult<T>(T d)
{
return new JsonResult<T>(d);
}
}

asp.net mvc RequireLocalHostActionFilter not fireing

Im trying to implement the RequireLocalHostActionFilter that Phil Haack did in one of his shows.
Its the ActionFilter thats checks if a call to a giving method is from the local host, and is registred in the global filters.
But my filter is not working, and I cant get my head around it.
So please, if someone has some free time to take a look.
My ActionFilter:
public class RequireLocalHostActionFilter : AuthorizeAttribute
{
protected override bool AuthorizeCore(HttpContextBase httpContext)
{
return !httpContext.Request.IsLocal; // I need to test on the local host, so I reverse the logic.
}
}
My FilterProvider
public class ConditionalFilterProvider : IFilterProvider
{
public readonly IEnumerable<Func<ControllerContext, ActionDescriptor, object>> _conditions;
public ConditionalFilterProvider(IEnumerable<Func<ControllerContext, ActionDescriptor, object>> conditions)
{
this._conditions = conditions;
}
public IEnumerable<Filter> GetFilters(ControllerContext controllerContext, ActionDescriptor actionDescriptor)
{
IEnumerable<Filter> result = from condition in _conditions
select condition(controllerContext, actionDescriptor)
into filter
where filter != null
select new Filter(filter, FilterScope.Global, null);
return result;
}
}
In globals.
public static void RegisterGlobalFilters(GlobalFilterCollection filters)
{
var conditions = new Func<ControllerContext, ActionDescriptor, object>[]
{
(c, a) =>
a.ControllerDescriptor.ControllerName.Equals("Online", StringComparison.OrdinalIgnoreCase)
? null : new RequireLocalHostActionFilter()
};
filters.Add(new ConditionalFilterProvider(conditions));
filters.Add(new HandleErrorAttribute());
}
I can see the action filter is added into the collection of filters.
And last my OnlineController, a simpel control, that I want the global filter to kick in.
public class OnlineController : Controller
{
public ActionResult Index()
{
ViewBag.Message = "Welcome to ASP.NET MVC! online";
return View();
}
// thx for taking your time to read this post.
// dennis
My if statement needed to be reversed.
var conditions = new Func < ControllerContext,
ActionDescriptor, object > [] {
(c, a) =>
a.ControllerDescriptor.ControllerName.Equals("Online",
StringComparison.OrdinalIgnoreCase) * * ? new RequireLocalHostActionFilterAttribute() : null * *
};
And I forgot to added the filter to the top of the controller.
// dennis

Resources