I found some similar questions and I like the solution with the "MultipleButtonAttribute" found at here: How do you handle multiple submit buttons in ASP.NET MVC Framework?
But I come up with another solution and I thought I share it with the community.
So first of all I make a ModelBinder that handle the incoming request.
I have to made a restriction. input/button element id and name must be a prefix of "cmd".
public class CommandModelBinder<T> : IModelBinder
{
public CommandModelBinder()
{
if (!typeof(T).IsEnum)
{
throw new ArgumentException("T must be an enumerated type");
}
}
public object BindModel(System.Web.Mvc.ControllerContext controllerContext, ModelBindingContext bindingContext)
{
string commandText = controllerContext.HttpContext.Request.Form.AllKeys.Single(key => key.StartsWith("cmd"));
return Enum.Parse(typeof (T), commandText.Substring(3));
}
}
Of course it can be changed or make it to configurable via web.config of App_Start.
The next thing I make is a HtmlHelper extension to generate the necessary HTML markup:
public static MvcHtmlString CommandButton<T>(this HtmlHelper helper, string text, T command)
{
if (!command.GetType().IsEnum) throw new ArgumentException("T must be an enumerated type");
string identifier = "cmd" + command;
TagBuilder tagBuilder = new TagBuilder("input");
tagBuilder.Attributes["id"] = identifier;
tagBuilder.Attributes["name"] = identifier;
tagBuilder.Attributes["value"] = text;
tagBuilder.Attributes["type"] = "submit";
return new MvcHtmlString(tagBuilder.ToString());
}
It's still a tech demo so the html attribute and the other super overloads waiting for you to develop on your own.
Now we have to make some enumerations to try our code. They can be general or controller specific:
public enum IndexCommands
{
Save,
Cancel
}
public enum YesNo
{
Yes,
No
}
Now pair the enumerations with the binders. I do it in different file in the App_Start folder. ModelBinderConfig.
ModelBinders.Binders.Add(typeof(IndexCommands), new CommandModelBinder<IndexCommands>());
ModelBinders.Binders.Add(typeof(YesNo), new CommandModelBinder<YesNo>());
Now after we set up everything, make an action to try the codes. I kept it simple so:
public ActionResult Index()
{
return View();
}
[HttpPost]
public ActionResult Index(IndexCommands command)
{
return View();
}
And my view looks like this:
#using (Html.BeginForm())
{
#Html.CommandButton("Save", IndexCommands.Save)
#Html.CommandButton("Cancel", IndexCommands.Cancel)
}
Hope this helps to keep your code clear, type safe and readable.
Related
Visual Studio's Intellisense knows about what View Files are available.
If I write this (typo intentional):
[HttpGet]
public ActionResult Create()
{
return View("Craete", new KeywordViewModel());
}
Then it will whinge, and not underline it and say "can't resolve View".
This is lovely.
If I want to add a layer of indirection, and pass "Create" in via another function:
[HttpGet]
public ActionResult Create()
{
return ArbitraryIndirection("Craete");
}
public ActionResult ArbitraryIndirection(string viewName)
{
return View(viewName, new KeywordViewModel());
}
Then I lose this behaviour. Intellisense doesn't know that ArbitraryIndirection is expecting a View target, so it doesn't check.
This isn't at all surprising, but it is sad.
Can I teach Intellisense to be cleverer?
Is there something that tells Intellisence that that param is special (I would guess an XML comment reference, or an attribute, maybe?) or is it magic hard-coded into Intellisence somewhere?
Can I get the write-time error-checking on the indirect View reference?
I haven't done this in about a year, but at that time, it was ReSharper which was cleverly verifying the views for me.
If you are using ReSharper, you can make use of the [AspMvcView] attribute in the JetBrains.Annotations assembly - which you can include via Nuget. A relevant guides appears on the jetbrains website at this link:
https://blog.jetbrains.com/dotnet/2011/12/07/providing-intellisense-navigation-and-more-for-custom-helpers-in-aspnet-mvc/
Example usage of the [AspMvcAction] and [AspMvcController] attributes follows (note you'll need the [AspMvcView] attribute instead, but I don't have code to hand using that)
public static MvcHtmlString WidgetWrapperAction(this HtmlHelper<dynamic> html, [AspMvcAction] string action, [AspMvcController] string controller, object parameters)
{
var routeValueDictionary = new RouteValueDictionary(parameters) { { "area", "AreaName" } };
var htmlString = html.Action(action, controller, routeValueDictionary);
if (string.IsNullOrWhiteSpace(htmlString.ToHtmlString()))
{
htmlString = html.Action("WidgetLoadFailed", "WidgetLoadFailed", new RouteValueDictionary { { "area", "AreaName" } });
}
return htmlString;
}
Hope this helps.
I am working in MVC4 and want to define a model using an Uppercase attribute. The idea would be that the presence of the Uppercase attribute would cause the model value to be converted to uppercase when it arrived at the server.
At the moment I have the following code within the model:
[Required]
[Display(Name="Account Code")]
[StringValidation(RegExValidation.AccountCode, Uppercase=true)]
public string Account
{
get { return _account; }
set
{
if (value != null)
_account = value.ToUpper();
}
}
But what I would really like is this:
[Required]
[Display(Name="Account Code")]
[StringValidation(RegExValidation.AccountCode)]
[Uppercase]
public string Account { get; set; }
I think that I may need to create the Uppercase attribute as a ValidationAttribute to ensure it gets fired when the model hits the server. But that seems a bit wrong, as I'm not really validating the data. Is there a better way?
Also, is there any way to ensure the invocation order on the attributes? I really want to convert the data to uppercase before the custom StringValidation attribute fires, as this checks the case of the text in the regex pattern.
To add a bit of background to this, I want to reduce the need to add code to uppercase the data. The nirvana would be a single attribute, which updates the data on the way into the server, either in the model binding or validation stage. This attribute can then be referenced in the StringValidation attribute to amend the RegEx value used in its checks. I can also then lookup this attribute in a custom TextBoxFor helper method, such that I can add text-transform: uppercase so it looks correct on the client side.
Does anyone have any ideas out there?
I have managed to get this working, to a point, so here's my solution for others to appraise.
Once point to note was that the full solution couldn't be achieved because I couldn't get the Modelmetadata inside the StringValidation.IsValid() attribute. The particular issue I had here was that I could get the Metadata, however I could not get the PropertyName from it, only the DisplayName. There were multiple options out there, but the fact that some of my properties have the same DisplayName means that I couldn't be sure that the ProprtyName was the one I was actually validating.
Here's the code for the ValidationAttribute:
public class StringValidationAttribute : ValidationAttribute, IClientValidatable, IMetadataAware {
private bool _uppercase;
public StringValidationAttribute(bool uppercase = false) {
_uppercase = uppercase;
}
...
public void OnMetadataCreated(ModelMetadata metadata)
{
metadata.AdditionalValues["Uppercase"] = _uppercase;
}
}
I then created a new IModelBinder implementation:
public class StringBinder : IModelBinder
{
public object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
{
ValueProviderResult result = bindingContext.ValueProvider.GetValue(bindingContext.ModelName);
if (result == null)
return null;
if (bindingContext.ModelMetadata.AdditionalValues.ContainsKey("Uppercase")) {
if ((bool)bindingContext.ModelMetadata.AdditionalValues["Uppercase"]])
return result.AttemptedValue.ToUpper();
}
return result.AttemptedValue;
}
}
And registered that in myGlobal.asax file:
ModelBinders.Binders.Add(typeof(string), new StringBinder());
The code so far will cause any string input coming into MVC to be converted to Uppercase if it has StringValidationAttribute attached to it on the model, and where the uppercase indicator has been set.
Next, to achieve my desire of making the html forms be uppercase too, I implemented a new EditorTemplate named string.cshtml. In this view I added:
RouteValueDictionary htmlAttributes = new RouteValueDictionary();
if ((bool)ViewData.ModelMetadata.AdditionalValues["Uppercase"]) {
htmlAttributes.Add("class", "Uppercase");
}
#Html.TextBox("", Model, htmlAttributes)
With the CSS as;
.Uppercase {
text-transform: uppercase;
}
Hope this post helps some others out there.
For Web API purpose it is better to convert the incoming json to uppercase or lowercase.
public class ToUpperCase : JsonConverter
{
public override bool CanConvert(Type objectType)
{
return objectType == typeof(string);
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
return reader.Value.ToString().ToUpper();
}
}
[Display(Name = "PNR NAME")]
[JsonConverter(typeof(Annotations.ToUpperCase))]
public string PNR { get; set; }
OR Globally;
protected void Application_Start()
{
AreaRegistration.RegisterAllAreas();
//.......... others
JsonMediaTypeFormatter jsonFormatter = GlobalConfiguration.Configuration.Formatters.JsonFormatter;
JsonSerializerSettings jSettings = new Newtonsoft.Json.JsonSerializerSettings();
jSettings.Converters.Add(new UpperCaseStringConverter());
jsonFormatter.SerializerSettings = jSettings;
}
You're right, ValidationAttribute is not the right fit. It seems like doing this at the Model Binding stage would be a better idea. See this article for a detailed explanation of how to customize this behavior.
Based on the information provided there, I believe you should be able to create an attribute based on CustomModelBinderAttribute like this:
[AttributeUsage(AttributeTargets.Property)]
public class UppercaseAttribute : CustomModelBinderAttribute
{
public override IModelBinder GetBinder()
{
return new UppercaseModelBinder();
}
private class UppercaseModelBinder : DefaultModelBinder
{
public override object BindModel(
ControllerContext controllerContext,
ModelBindingContext bindingContext)
{
var value = base.BindModel(controllerContext, bindingContext);
var strValue = value as string;
if (strValue == null)
return value;
return strValue.ToUpperInvariant();
}
}
}
I have not tested this. Let me know if it works or not.
NOTE:
I'm adding on to this post because until I discovered the approach I now use, I read this and tried all above unsuccessfully.
I generally use a two part process when dealing with forcing text data to be formatted as uppercase. 1. at the view and 2. at the controller
At the view layer so that the user knows data is going to be used in the uppercase form. This can be down through htmlAttributes used in the EditorFor HTML helper.
#HTML.EditorFor(model => model.Access_Code, new { htmlAttributes = new Style= "text-transform:uppercase"}})
Now this only forces the data seen and entered by the user to uppercase and not the data sent to the server. To do that requires some code in the associated method in the controller.
I add the ToUpper() method to the target attribute of the object being passed back to the contoller. Here is hypothetical example showing this.
public ActionResult verify(int? id)
{
var userData = db.user.Where (i=> i.userID == id).Single();
userData.Access_Code = userData.Access_Code.ToUpper();
...
}
Below, in CreateTest, uponsuccessful, I want to redirect to Tests from CreateTest.
I want to do something like the following:
public ActionResult Tests(int ID, string projectName)
{
TestModel model = new TestModel (ID, projectName);
return View(model);
}
[HttpPost]
public ActionResult CreateTest(TestModel model)
{
try
{
return RedirectToAction("Tests");
}
catch (Exception e)
{
ModelState.AddModelError("Error", e.Message);
return View(model);
}
}
You might need to provide the arguments when redirecting:
return RedirectToAction("Tests", new {
ID = model.ID,
projectName = model.ProjectName
});
and the url you will be redirecting to will now look something like this:
/Foo/Tests?ID=123&projectName=abc
I know this is a bit old but...
What I've done in the past is have a "MessageArea" class exposed as a property on my base controller that all my controllers ultimately inherit from. The property actually stores the class instance in TempData. The MessageArea has a method to Add() which takes a string message and an enum Type (e.g. Success, Error, Warning, Information).
I then have a partial that renders whatever messages are in MessageArea with appropriate styling according to the type of the message.
I have a HTMLHelper extension method RenderMessageArea() so in any view I can simple say #Html.RenderMessageArea(), the method and partial take care of nulls and nothing is output if there are no messages.
Because data stored in TempData only survives 1 request it is ideal for cases where you want your action to redirect but have 1 or more messages shown on the destination page, e.g. an error, not authorised page etc... Or if you add an item but then return to the index list page.
Obviously you could implement something similar to pass other data. Ultimately I'd say this is a better solution to the original question than the accepted answer.
EDIT, EXAMPLE:
public class MessageAreaModel {
public MessageAreaModel() {
Messages = new List<Message>();
}
public List<Message> Messages { get; private set; }
public static void AddMessage(string text, MessageIcon icon, TempDatadictionary tempData) {
AddMessage(new Message(icon, text), tempData);
}
public static void AddMessage(Message message, TempDataDictionary tempData) {
var msgArea = GetAreaModelOrNew(tempData);
msgArea.Messages.Add(message);
tempData[TempDataKey] = msgArea;
}
private static MessageAreaModel GetAreaModelOrNew(TempDataDictionary tempData) {
return tempData[TempDataKey] as MessageAreaModel ?? new MessageAreaModel();
}
The above class can then be used to add messages from your UI layer used by the controllers.
Then add an HtmlHelper extension like so:
public static void RenderMessageArea(this HtmlHelper html) {
html.RenderPartial("MessageArea",
(MessageAreaModel)html.ViewContext.TempData[MessageAreaModel.TempDataKey] ?? MessageAreaModel.Empty);
html.ViewContext.TempData.Remove(MessageAreaModel.TempDataKey);
}
The above is not fully completed code there are various bells and whistles I've left out but you get the impression.
Make the int nullable:
public ActionResult Tests(int? ID, string projectName){
//...
}
I edited my whole question, so do not wonder :)
Well, I want to have an ActionResult that takes domain model data and some additional parameters, i.e page index and page size for paging a list. It decide itself if it returns a PartialViewResult or a ViewResult depending on the kind of web request (ajax request or not).
The reffered data shall be mapped automatically by using an IMappingService, which is responsible for transforming any domain model data into a view model.
The MappingService uses AutoMapper for simplicity.
MappingActionResult:
public abstract class MappingActionResult : ActionResult
{
public static IMappingService MappingService;
}
BaseHybridViewResult:
public abstract class BaseHybridViewResult : MappingActionResult
{
public const string defaultViewName = "Grid";
public string ViewNameForAjaxRequest { get; set; }
public object ViewModel { get; set; }
public override void ExecuteResult(ControllerContext context)
{
if (context == null) throw new ArgumentNullException("context");
var usePartial = ShouldUsePartial(context);
ActionResult res = GetInnerViewResult(usePartial);
res.ExecuteResult(context);
}
private ActionResult GetInnerViewResult(bool usePartial)
{
ViewDataDictionary viewDataDictionary = new ViewDataDictionary(ViewModel);
if (String.IsNullOrEmpty(ViewNameForAjaxRequest))
{
ViewNameForAjaxRequest = defaultViewName;
}
if (usePartial)
{
return new PartialViewResult { ViewData = viewDataDictionary, ViewName = ViewNameForAjaxRequest };
}
return new ViewResult { ViewData = viewDataDictionary };
}
private static bool ShouldUsePartial(ControllerContext context)
{
return context.HttpContext.Request.IsAjaxRequest();
}
}
AutoMappedHybridViewResult:
public class AutoMappedHybridViewResult<TSourceElement, TDestinationElement> : BaseHybridViewResult
{
public AutoMappedHybridViewResult(PagedList<TSourceElement> pagedList)
{
ViewModel = MappingService.MapToViewModelPagedList<TSourceElement, TDestinationElement>(pagedList);
}
public AutoMappedHybridViewResult(PagedList<TSourceElement> pagedList, string viewNameForAjaxRequest)
{
ViewNameForAjaxRequest = viewNameForAjaxRequest;
ViewModel = MappingService.MapToViewModelPagedList<TSourceElement, TDestinationElement>(pagedList);
}
public AutoMappedHybridViewResult(TSourceElement model)
{
ViewModel = MappingService.Map<TSourceElement, TDestinationElement>(model);
}
public AutoMappedHybridViewResult(TSourceElement model, string viewNameForAjaxRequest)
{
ViewNameForAjaxRequest = viewNameForAjaxRequest;
ViewModel = MappingService.Map<TSourceElement, TDestinationElement>(model);
}
}
Usage in controller:
public ActionResult Index(int page = 1)
{
return new AutoMappedHybridViewResult<TeamEmployee, TeamEmployeeForm>(_teamEmployeeRepository.GetPagedEmployees(page, PageSize));
}
So as you can see the IMappingService is hidden. The controller should not know anything about the IMappingService interface, when AutoMappedHybridViewResult is used.
Is the MappingActionResult with the static IMappingServer appropriate or am I violating the DI principle?
I think a better design is to have a ViewResultFactory that depends on IMappingService, then you can inject that into your controller. Then you call it like so:
public class MyController : Controller
{
IViewResultFactory _viewResultFactory;
ITeamEmployeeRepository _teamEmployeeRepository;
public MyController(IViewResultFactory viewResultFactory)
{
_viewResultFactory = viewResultFactory;
}
public ActionResult MyAction(int page, int pageSize)
{
return
_viewResultFactory.GetResult<TeamEmployee, TeamEmployeeForm>(
_teamEmployeeRepository.GetPagedEmployees(page, pageSize));
}
}
The implementation would like this (you would need to create overloads for each of your HybridViewResult constructors):
public HybridViewResult<TSourceElement, TDestinationElement> GetResult<TSourceElement, TDestinationElement>(PagedList<TSourceElement> pagedList)
{
return new HybridViewResult<TSourceElement, TDestinationElement>(_mappingService, pagedList);
}
That way you hide the implementation from your controllers, and you don't have to depend on the container.
There are a few different points that you could inject IMappingService. http://codeclimber.net.nz/archive/2009/04/08/13-asp.net-mvc-extensibility-points-you-have-to-know.aspx is a good site for help in picking the appropriate extensibility points for .NET MVC.
If you want to stick with having this functionality be a derived ActionResult, then I think you could put the dependency in the ActionInvoker if you want to, but the Controller makes more sense to me. If you don't want the IMappingService in the Controller, you could always wrap it in a HybridViewResultFactory, and access that object in the Controller. In that case your shortcut methods would look like:
public HybridViewResult<TSourceElement, TDestinationElement> AutoMappedHybridView<TSourceElement,TDestinationElement>(PagedList<TSourceElement> pagedList, string viewNameForAjaxRequest)
{
HybridViewResultFactory.Create<TSourceElement, TDestinationElement>(pagedList, viewNameForAjaxRequest);
}
etc.
I'm not sure why you need to use an ActionResult, but if there is no reason that makes it explicitly necessary, you could create a HybridViewModel class and a HybridViewModelBinder class that is injected with the mapping service dependency.
I am assuming you want to use constructor injection, but if you have the StructureMap dependency in your UI assembly, you could access a static dependency resolver class (like Clowers said).
This question would be easier to give a definite answer to if I understood why you using an ActionResult.
It seems like you are using the action result to handle two functionalities that do not necessarily go together all the time, and that could be used separately. Also, there is not a clear indication that it needs to be in an ActionResult.
Presumably, you could (a) leverage the Automapper functionality for results other than html (ViewResult) output, and (b) you could leverage the functionality of auto-detecting ajax requests without needing to automap the model.
It seems to me like the automapping of the view model could be used to inject the view model into the controller action directly, thus removing the controller's dependency on the IMappingService. What you would need is a ModelBinder class to be injected with your IMappingService (the implementation of which I assume contains a repository or datastore type dependency).
Here is a good article explaining how to leverage model binders: http://odetocode.com/blogs/scott/archive/2009/04/27/6-tips-for-asp-net-mvc-model-binding.aspx.
Then you can overwrite the DefaultModelBinder in the classes that need to be Automapped as follows:
public ActionResult DoItLikeThis([AutoMap(typeof(MyDomainModelClass))]MyViewModelClass viewModel){
//controller action logic
}
Now, regarding the HybridViewResult, I would suggest that you handle this with an Action Filter instead. So, you could just use ActionResult or ViewResultBase as the Result type of your action method and decorate it with an action filter, i.e.:
[AutoSelectViewResult]
public ViewResultBase AndDoThisLikeSo(){
//controller action logic
}
I think overall this will be a much better solution than coupling these two functionalities to an ActionResult.
I'm not sure if this behavior is expected or not, but it seems that custom model binding doesn't work when the binding is assigned to an interface type. Has anyone experimented with this?
public interface ISomeModel {}
public class SomeModel : ISomeModel {}
public class MvcApplication : HttpApplication {
protected void Application_Start(object sender, EventArgs e) {
ModelBinders.Binders[typeof(ISomeModel)] = new MyCustomModelBinder();
}
}
With the above code when I bind to a model of type SomeModel, MyCustomModelBinder is never hit; however, if I change the above code and substitute typeof(ISomeModel) for typeof(SomeModel) and post the exact same form MyCustomModelBinder is called as expected. Does that seem right?
Edit
I found myself back in this predicament over a year after I originally asked this question, and now I have a solution that works. Thank you Matt Hidinger!
http://www.matthidinger.com/archive/2011/08/16/An-inheritance-aware-ModelBinderProvider-in-MVC-3.aspx
I was experimenting with this issue and I came up with a solution of sorts. I made a class called InterfaceModelBinder:
public class InterfaceModelBinder : DefaultModelBinder
{
public override object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
{
ModelBindingContext context = new ModelBindingContext(bindingContext);
var item = Activator.CreateInstance(
Type.GetType(controllerContext.RequestContext.HttpContext.Request.Form["AssemblyQualifiedName"]));
Func<object> modelAccessor = () => item;
context.ModelMetadata = new ModelMetadata(new DataAnnotationsModelMetadataProvider(),
bindingContext.ModelMetadata.ContainerType, modelAccessor, item.GetType(), bindingContext.ModelName);
return base.BindModel(controllerContext, context);
}
}
Which I registered in my Application_Start as so:
ModelBinders.Binders.Add(typeof(IFormSubmission), new InterfaceModelBinder.Models.InterfaceModelBinder());
The interface and a concrete implementation look like this:
public interface IFormSubmission
{
}
public class ContactForm : IFormSubmission
{
public string Name
{
get;
set;
}
public string Email
{
get;
set;
}
public string Comments
{
get;
set;
}
}
The only downside to this whole approach (as you might have gathered already) is that I need to get the AssemblyQualifiedName from somewhere, and in this example it is being stored as a hidden field on the client side, like so:
<%=Html.HiddenFor(m => m.GetType().AssemblyQualifiedName) %>
I'm not certain though that the downsides of exposing the Type name to the client are worth losing the benefits of this approach. An Action like this can handle all my form submissions:
[HttpPost]
public ActionResult Process(IFormSubmission form)
{
if (ModelState.IsValid)
{
FormManager manager = new FormManager();
manager.Process(form);
}
//do whatever you want
}
Any thoughts on this approach?
Suddenly, an MVC3 solution appears:
http://www.matthidinger.com/archive/2011/08/16/An-inheritance-aware-ModelBinderProvider-in-MVC-3.aspx
I'm not sure if its directly related but yes there are things that you need to think about when using model binding and interfaces... I ran into similar problems with the default model binder, but it may not be directly related depending on how you are doing things...
Have a look at the following:
ASP.net MVC v2 - Debugging Model Binding Issues - BUG?
ASP.net MVC v2 - Debugging Model Binding Issues - BUG?