Use nameof() to get a attributepath for ModelState.AddModelError() - c#

I like to use nameof() in my ASP.NET Core application when setting mvc errors using ModelState.AddModelError(). This reduces hidden errors since I pass the name of an attribute here. Compared to a string, this has the benefit that a compiler error is thrown when the attribute was renamed.
Example
class MyModel{
public string Name{get;set;}
}
public class MyController:Controller{
public IActionResult Test(MyModel model) {
// Will hiddenly fail when "Name" is renamed to something else
ModelState.AddModelError("Name", "The Name field is required!");
// Better: Using the auto renaming feature of VS, this will be updated if "Name" was renamed
ModelState.AddModelError(nameof(model.Name), "The Name field is required!");
}
}
This works, until I have another entity:
class MyModel{
public string Name{get;set;}
public MyOtherModel OtherModel{get;set;}
}
class MyOtherModel{
public string OtherName{get;set;}
}
Now I want to get the name of OtherModel. The problem: ASP.NET requires BaseClass.ChildClass pattern here, so OtherModel.OtherName in this example. Using the same logic like above:
public class MyController:Controller{
public IActionResult Test(MyModel model) {
ModelState.AddModelError(nameof(model.MyOtherModel.OtherName), "The Name field is required!");
}
}
will just give me the name of the child attribute, so OtherName in this example. But I need OtherModel.OtherName. Is there a clean way to get this without building the string itself?
This would be possible doing something like this:
string actionName = nameof(model.MyOtherModel) + "." + nameof(model.MyOtherModel.OtherName);
But not a very clean and intuitive way imho.

As others have suggested, you will probably have to write your own method to do this. This is what I came up with using Linq.Expressions:
string NameOfMember(Expression<Func<Model, object>> accessor)
{
string str = accessor.Body.ToString();
return str.Substring(str.IndexOf('.') + 1);
}
You would then use it like this: NameOfMember(model => model.MyOtherModel.OtherName)
I'm not familiar with ASP.NET but you can change Model to any type you want, including object.
As a side note, you shouldn't repeat "The Name field is required!" in your code. Maybe you can include that in your helper method as well.

Related

Asp.Net Core Error: InvalidOperationException: The view '11c8d3ac-b5f4-47c0-8d68-b1704f2fdc43' was not found [duplicate]

I know this seems pretty basic, and it should be, but I can't find out where I am going wrong. (I hve read other articles with similar titles on SO, and other resources on the web but still cant figure it out).
I have a controller and in it I am setting a string variable. Now I don't mind if this takes the form of a property, an ActionResult, or a straight method. I just want a simple string that I can play with in the controller, and return it to the view.
Essentially what I am trying to do is list the files in a given folder. So my logic is like this:
Find the current folder (partially successful)
Append the path to the where the files you want to located are. i.e. if my current folder is Web\ then I will append something like "Content\CSS" if I wanted to list all the CSS files for example. (I do this because I want to allow the user to dynamically change the site by selecting the css to apply). So it would look like:
CurrentPath += "Content\CSS"
I want load the file names into an array or list
I want to pass this list to my view to render in a combo box (which is on my _Layout.cshtml).
It is important to know that I am trying to view the string on the _Layout.cshtml as I cant just build another view for it. (Unless I am wrong, in that case I would appreicate any help).
At the moment I am still working on getting a simple string passed to my view in a way I can freely manipulate it like in step 2.
I started off with a separate static class and a global variable:
public static class MyTheme
{
public static string CurrentPath = HostingEnvironment.MapPath("~");
}
In my view I had:
#Html.Label(MyProject.Web.Controllers.MyTheme.CurrentPath);
This worked but when I tried to use an if statement to determine if the string was null or empty I got errors. So my next attempts all failed.
Next I decided to bring it into a controller (in this case my BaseController) and this is when I started running into problems. Code below:
Inside BaseController Class
public ActionResult ThemePath()
{
string currentPath = Server.MapPath("~");
if (string.IsNullOrEmpty(currentPath))
{
currentPath = "Error!";
}
else
{
currentPath = "Our Path Is: " + currentPath;
}
return View(currentPath);
}
I dont know how to access and run this from inside my _Layout.cshtml view
So next I tried a standard method inside BaseController:
public string ThemePath()
{
string currentPath = Server.MapPath("~");
if (string.IsNullOrEmpty(currentPath))
{
currentPath = "Error!";
}
else
{
currentPath = "Our Path Is: " + currentPath;
}
return currentPath;
}
Again I don't know how to access it in the view
Finally I tried to use ViewBag and ViewData and now I am just going bonkers! So in my base controller I have:
public string ThemePath()
{
ViewBag.currentPath = Server.MapPath("~");
if (string.IsNullOrEmpty(ViewBag.currentPath))
{
ViewBag.currentPath = "Error!";
}
else
{
ViewBag.currentPath = "Our Path Is: " + ViewBag.currentPath;
}
return ViewBag.currentPath;
}
and in my view I have
#Html.Label(ViewBag.CurrentPath);
or even
#Html.Label(ViewBag.CurrentPath.ToString());
With the following friendly little error messages:
CS1973: 'System.Web.Mvc.HtmlHelper' has no applicable method named 'Label' but appears to have an extension method by that name. Extension methods cannot be dynamically dispatched. Consider casting the dynamic arguments or calling the extension method without the extension method syntax.
Finally I tried ViewData in the base as follows:
public string ThemePath()
{
ViewData["currentPath"] = Server.MapPath("~");
if (string.IsNullOrEmpty(ViewData["currentPath)"].ToString()))
{
ViewData["currentPath"] = "Error!";
}
else
{
ViewData["currentPath"] = "Our Path Is: " + ViewData["currentPath"];
}
return ViewData["currentPath"].ToString();
}
and correspondingly in the _Layout.cshtml I tried:
#Html.Label(ViewData["CurrentPath"].ToString());
Without the .ToString() I get the above error:
With the .ToString() I get a null refrence execption error.
Where am I going wrong?
To pass a string to the view as the Model, you can do:
public ActionResult Index()
{
string myString = "This is my string";
return View((object)myString);
}
You must cast it to an object so that MVC doesn't try to load the string as the view name, but instead pass it as the model. You could also write:
return View("Index", myString);
.. which is a bit more verbose.
Then in your view, just type it as a string:
#model string
<p>Value: #Model</p>
Then you can manipulate Model how you want.
For accessing it from a Layout page, it might be better to create an HtmlExtension for this:
public static string GetThemePath(this HtmlHelper helper)
{
return "/path-to-theme";
}
Then inside your layout page:
<p>Value: #Html.GetThemePath()</p>
Hopefully you can apply this to your own scenario.
Edit: explicit HtmlHelper code:
namespace <root app namespace>
{
public static class Helpers
{
public static string GetThemePath(this HtmlHelper helper)
{
return System.Web.Hosting.HostingEnvironment.MapPath("~") + "/path-to-theme";
}
}
}
Then in your view:
#{
var path = Html.GetThemePath();
// .. do stuff
}
Or:
<p>Path: #Html.GetThemePath()</p>
Edit 2:
As discussed, the Helper will work if you add a #using statement to the top of your view, with the namespace pointing to the one that your helper is in.
Use ViewBag
ViewBag.MyString = "some string";
return View();
In your View
<h1>#ViewBag.MyString</h1>
I know this does not answer your question (it has already been answered), but the title of your question is very vast and can bring any person on this page who is searching for a query for passing a simple string to View from Controller.
Why not create a viewmodel with a simple string parameter and then pass that to the view? It has the benefit of being extensible (i.e. you can then add any other things you may want to set in your controller) and it's fairly simple.
public class MyViewModel
{
public string YourString { get; set; }
}
In the view
#model MyViewModel
#Html.Label(model => model.YourString)
In the controller
public ActionResult Index()
{
myViewModel = new MyViewModel();
myViewModel.YourString = "However you are setting this."
return View(myViewModel)
}
#Steve Hobbs' answer is probably the best, but some of your other solutions could have worked. For example,
#Html.Label(ViewBag.CurrentPath); will probably work with an explicit cast, like #Html.Label((string)ViewBag.CurrentPath);. Also, your reference to currentPath in #Html.Label(ViewData["CurrentPath"].ToString()); is capitalized, wherein your other code it is not, which is probably why you were getting null reference exceptions.
Just define your action method like this
public string ThemePath()
and simply return the string itself.
If you are trying to simply return a string to a View, try this:
public string Test()
{
return "test";
}
This will return a view with the word test in it. You can insert some html in the string.
You can also try this:
public ActionResult Index()
{
return Content("<html><b>test</b></html>");
}

Web API model validation and default values

This is the spiritual successor to my previous question Web API attribute routing and validation - possible?, which I think was too general to answer. Most of those issues are solved, but the default value question remains.
Basically I have solved many pieces of the puzzle. I have this:
[HttpGet]
[Route("test/{id}"]
public IHttpActionResult RunTest([FromUri]TestRequest request)
{
if (!ModelState.IsValid) return BadRequest(ModelState);
return Ok();
}
My TestRequest class:
public class TestRequest
{
public string id { get; set; }
[DefaultValue("SomethingDefault")]
public string something { get; set; }
}
The problem is that if no parameter is in the query string for something, the model is "valid" and yet something is null.
If I specify a blank value for something (i.e. GET test/123?something=), then the default value comes into play, and the model is valid again.
Why is this? How can I get a default value into my model here? As a bonus, why is it when a parameter is not specified, the default value is not used, but when a blank string is explicitly specific, the default value is used?
(I've been trawling through the ASP.NET stack source code and am knee-deep in model binders and binding contexts. But my best guess can't be right - it looks like the DefaultValueAttribute is used only if the parameter value is null. But that's not the case here)
You need to initialize the default value in the constructor for your Model:
public class TestRequest
{
public TestRequest()
{
this.something = "SomethingDefault";
}
public string id { get; set; }
[DefaultValue("SomethingDefault")]
public string something { get; set; }
}
Update:
With C# 6, you don't need to initialize it in the constructor anymore. You can assign the default value to the property directly:
public class TestRequest
{
public string id { get; set; }
[DefaultValue("SomethingDefault")]
public string something { get; set; } = "SomethingDefault";
}
As documentation of the DefaultValueAttribute states:
Note
A DefaultValueAttribute will not cause a member to be
automatically initialized with the attribute's value. You must set the
initial value in your code.
In the case where you're providing no value for your something property, the property is initialized and the ModelBinder doesn't have a value to assign to it and thus the property defaults to its default value.
Specifying the default in the constructor works for when no parameter is specified at all, but when a blank string is specified, null is put into the field instead.
As such, adding [DefaultValue("")] actually worked the best - when a blank string was specified, a blank string was passed in. Then the constructor can specify default values for when the parameter is missing.
To get around this, I've created PreserveBlankStringAttribute, derives from DefaultValueAttribute which is equivalent to [DefaultValue("")].
I would very much welcome a better answer than this, please.

ASP.NET MVC Modify view model property before validation

I've been googling like crazy without result, maybe I'm just missing the correct keywords.
I have a class with a custom validation attribute on a property. I want to "clean" the value before validation, removing the white-space and special characters that we accept but that we don't want to save to the database.
public class PersonViewModel
{
[SocialSecurityNumberLuhn(ErrorMessage = "Incorrect social security number")]
public string SocialSecurityNumber { get; set; }
}
I would want to do something like this:
public class PersonViewModel
{
[CleanWhiteSpace]
[SocialSecurityNumberLuhn(ErrorMessage = "Incorrect social security number")]
public string SocialSecurityNumber { get; set; }
}
For example 1985-03-15-1234 should be saved and validated as 19850315-1234.
Any suggestions? What's the neatest approach?
If you change the auto-implemented property into a manual-implemented property then you can perform the "cleaning" step when the value is set, so it can only be stored in the model in a "clean" state. Something like this:
public class PersonViewModel
{
private string _socialSecurityNumber;
[SocialSecurityNumberLuhn(ErrorMessage = "Incorrect social security number")]
public string SocialSecurityNumber
{
get { return _socialSecurityNumber; }
set
{
_socialSecurityNumber = CleanSocialSecurityNumber(value);
}
}
}
The recommended approach here is to use a service layer. View models should not include any logic. With a service layer Your controller would call a method on its associated service and this method would return your view model with the clean SSN.
In this tutorial you will see how the service layer can be structured and you could adapt the pattern to clean your data before validation.
http://www.asp.net/mvc/tutorials/older-versions/models-(data)/validating-with-a-service-layer-cs
The use of a ValidationAttribute in your example would be an incorrect usage also.
Controller where you would inject / instantiate the service class
public ActionResult GetPerson(int PersonId){
return _personService.GetPerson(personId);
}
The service method
public PersonViewModel GetPerson(int Id){
// get the data (maybe from DAL) and clean returning view model
return new PersonViewModel(){SocialSecurityNumber = Clean(...)};
}
Hope this gives you some direction.

Decoration on ViewModel property to use a different name for binding

On MVC3, is there a way to decorate a ViewModel property in order to get the DefaultModelBinder to use a different name for it in the request?
For example, suppose you have the following view model:
public class SomeModel
{
public string Direction {get;set;}
}
But the parameter coming in is Dir from an external source (such as some third-party component, for example).
I know a custom model binder could handle that, but I assume there must be a way to decorate the property, similar to the way action parameters can use Bind(Prefix="...") in order to define that mapping.
You could always create another Property:
public class SomeModel
{
public string Direction {get;set;}
public string Dir
{
get { return this.Direction; }
set { this.Direction = value; }
}
}
I'd also mention that the ViewModel used in a view (cshtml/vbhtml) does not have to be the same ViewModel used on the Post Method.
OK, so after more research looking at similar questions and seeing the feedback here as well, it seems that the answer to my question is basically "NO".
There is no out-of-the-box way, so either custom binders must be used or or the properties should be renamed.
A similar question with a more detailed answer can be found here: How to bind URL parameters to model properties with different names
I was able to accomplish this in ASP.NET MVC Core using the FromForm attribute.
public class DataTableOrder
{
public int Column { get; set; }
[FromForm(Name = "Dir")]
public string Direction { get; set; }
}
Documentation: https://docs.asp.net/en/latest/mvc/models/model-binding.html#customize-model-binding-behavior-with-attributes
However, depending if you do a GET or a POST, you might want to use [FromQuery] instead of [FromForm] I suppose.

asp.net mvc json serialization attributes

How to change the name of the field names in Json output
I want the Class name cook and field "time1" to be something else please. I am using asp.net mvc controller class to return the json result.
public class Cook
{
public string time1;
public string time2;
public string time3;
}
What about something like this...
public ActionResult GetCook()
{
var cook = new Cook();
return Json(new
{
atime = cook.time1,
anothertime = cook.time2,
yetanothertime = cook.time3
});
}
You can write your custom Attribute and access that value through ViewData.ModelMetadata (make sure that your attribute class implements IMetadataAware). Then create your own JSON class which puts all your properties and its values inside a dictionary. The key for each property will be the name that you specify in your custom attribute.
This sounds a little cryptical maybe, but if you think you like this approuch I can give you a code example. Good luck!

Categories

Resources