Teach ASP.NET MVC to treat key-less query values as boolean flags - asp.net-mvc-3

If a query value in URL doesn't have a key, e.g.: http://site.com?update
and an action has bool argument named update.
I want that update argument to received value true if 'update' value is present in the URL, and false otherwise.
Example:
Action: public ActionResult MyAction(string name, bool update) {...}
URL: http://site.com/path?name=Bob&update
Expected action call: controller.MyAction("Bob", true);
if URL is http://site.com/path?name=Bob (notice no update)
then expected call is controller.MyAction("Bob", false);
It is not a big deal, I do know I can just get Request.Query and find values with key=null, but I want to have it done through the framework.
Where do I begin?
I'm using ASP.NET MVC 3

Doing this through the framework would probably require you to implement your own value provider with a corresponding value provider factory and add the factory to ValueProviderFactories. You would probably implement it similar to the existing QueryStringValueProviderFactory and QueryStringValueProvider but then add your own implementation of GetValue that includes the additional logic you wanted to return true/false based on if there is a value provided for the query string key. Here is a link on adding a value provider, and check out the QueryStringValueProvider in the framework.
http://mgolchin.net/posts/19/dive-deep-into-mvc-ivalueprovider

Related

How to use a Database Generated Identity Key in Web Api OData

I've managed to create number of readonly Web Api OData services following the tutorials here: http://www.asp.net/web-api/overview/odata-support-in-aspnet-web-api. I'm therefore employing the ODataConventionModel builder to create the model from a set of entities (incidentally coming from a Telerik ORM). This all seems to work fine and I can happily issue queries, view the metadata and so forth on the service.
I've now tried to turn my attention to the other CRUD operations - firstly Create and have stumbled into a problem! Namely, the Post method fires correctly (CreateEntity) but the entity parameter is null - by doing a check against the ModelState.IsValid, it shows that the problem is a null ID (key) value. This is unsurprising because the database uses a Database Generated Identity for the ID column and therefore the ID would be created when the entity is saved into the database context.
I've therefore tried all sorts of ways of marking the ID column as database generated, but haven't managed to find anything. Strangely, I can't seem to find even one post of someone asking for this - surely I can't be the only one?!
I noted that when looking at the EF modelbuilder (for example here: http://forums.asp.net/t/1848984.aspx/1) there appears to be a means of affecting the model builder with a .HasDatabaseGeneratedOption property, but no similar option exists in the System.Web.Http.OData equivalent.
So the questions therefore are:
Is there a means of altering the model builder (or something else) so that the controller will accept the object and deserialize the entity even with a null key value?
If so, how can I do this?
If not, any suggestions as to other options?
I realise that I could potentially just populate the object with an (in this case) integer value from the client request, but this seems a) semantically wrong and b) won't necessarilly always be possible as a result of the client toolkit that might be used.
All help gratefully received!
Many thanks,
J.
You need to create a viewmodel for insert which does not contain the ID parameter. Use Automapper to map the properties of the incoming insert-model to your data entities.
The problem that you're having is that ID is a required attribute in your data model because it is your PK, except during insert, where it shouldn't be specified.
In my case, my database-generated key is a Guid.
As a work-around, in my TypeScript client code, I submit (via http POST) the object with an empty Guid like this: Note: ErrorId is the key column.
let elmahEntry: ELMAH_Error = {
Application: 'PTUnconvCost',
Host: this.serviceConfig.url,
Message: message,
User: that.userService.currentUserEmail,
AllXml: `<info><![CDATA[\r\n\r\n${JSON.stringify(info || {})}\r\n\r\n]]></info>`,
Sequence: 1,
Source: source,
StatusCode: 0,
TimeUtc: new Date(Date.now()),
Type: '',
ErrorId: '00000000-0000-0000-0000-000000000000'
};
Then, in my WebApi OData controller, I check to see if the key is the empty guid, and if so, I replace it with a new Guid, like this:
// POST: odata/ELMAH_Error
public IHttpActionResult Post(ELMAH_Error eLMAH_Error)
{
if (eLMAH_Error.ErrorId == Guid.Empty)
{
eLMAH_Error.ErrorId = Guid.NewGuid();
}
if (!ModelState.IsValid)
{
return BadRequest(ModelState);
}
db.ELMAH_Error.Add(eLMAH_Error);
try
{
db.SaveChanges();
}
catch (DbUpdateException)
{
if (ELMAH_ErrorExists(eLMAH_Error.ErrorId))
{
return Conflict();
}
else
{
throw;
}
}
return Created(eLMAH_Error);
}

AutoMapper UseValue, ResolveUsing or MapFrom reusing value from previous call. Is it caching or something?

I have a situation where starting from the 2nd call and subsequent ones (Ajax GET calls), AutoMapper is reusing the previous value (the value from the 1st call that comes from a click in an action link). It's like a "caching" problem...
public virtual ActionResult List(int assessmentId, int? chapterId, bool? isMenuClick)
{
Mapper.CreateMap<Element, AssessmentQuestionViewModel>().
ForMember(dest => dest.AssessmentId, opt => opt.MapFrom(e => assessmentId));
...
}
It doesn't matter if I use UseValue, ResolveUsing or MapFrom in the above opt => lambda. The behavior is the same, that is, it reuses the value from previous calls.
AssessmentId property does not exist in the source type ( Element ). This way I try to assign AssessmentId a value that "may" change dynamically during subsequent calls to the method where I have this code. assessmentId is a parameter in my ASP.NET MVC action method as shown above in the method signature.
Then I call this code in the List action method:
var questions =
Mapper.Map<IEnumerable<Element>, IEnumerable<AssessmentQuestionViewModel>>
(Database.Elements.Where(e => !elementIds.Contains(e.ElementId) &&
e.Standard.ChapterId == chapterId));
The first time, questions is OK, that is, all AssessmentQuestionViewModel objects have the AssessmentId property set correctly as per the CreateMap defined.
Starting from the 2nd call, it reuses the assessmentId from the 1st call and it messes up with my business logic because I expect it to map AssessmentId to the updated assessmentId that's being passed as a parameter to the List method.
Just to be sure: I've set a breakpoint in the code and I can see that the value of the assessmentId parameter is correct. It's just the returned mapped objects questions that have the wrong value in the AssessmentId property - a value that differs from the current assessmentId value. The values should be equal as I understand it since I'm asking AutoMapper to do the mapping using that current value.
I have AutoMapper 2.2.1-ci9000 (Prerelease), but I tested this with the previous version and I saw this same behavior. I updated to the Prerelease thinking that this "misbehavior" would go away.
I think this is a bug. Please correct me if I'm wrong or if I'm trying to use it in a way not supported. :)
I think the problem here your trying to create multiple mappings of the same type - which AutoMapper doesn't support. Everytime your List action is called, you create a new mapping (which has a different ForMember(...) clause). AutoMapper won't throw an exception it just ignores the duplicate mapping so what you are seeing here isn't a bug, it's expected behaviour.
ForMember is infact called on every map, however, you have a scoping issue here as your variable is hard-coded into the expression. As a work-around you could do something like:
public class MyController
{
public MyController()
{
// define mapping once, but make assessment expression dynamic
Mapper.CreateMap<Element, AssessmentQuestionViewModel>().
ForMember(dest => dest.AssessmentId, opt => opt.MapFrom(e => GetCurrentAssessmentId()));
}
private int GetCurrentAssessmentId()
{
return (int)TempData["AssessmentId"];
}
public ActionResult List(int assessmentId, ...)
{
// store current assessment temporarily
TempData.Add("AssessmentId", assessmentId);
// execute mapping
var questions = Mapper.Map<IEnumerable<Element>, IEnumerable<AssessmentQuestionViewModel>>
(Database.Elements.Where(e => !elementIds.Contains(e.ElementId) &&
e.Standard.ChapterId == chapterId));
}
}
I will say though, your jumping through a lot of hoops for this to work, it would be much simpler to manually set the property without the help of AutoMapper e.g.
var questions = Mapper.Map<IEnumerable<Element>, IEnumerable<AssessmentQuestionViewModel>>(...);
foreach (var q in questions)
{
q.AssessmentId = assessmentId;
}

Generic action result with Bind

Is it possible to have a ActionResult with a signature of:
[HttpPost]
public ActionResult SomeAction<T>([Bind(Prefix = typeof(T).Name)] T data)
{
MapAndUpdateModel<T>(data);
return Content(Boolean.TrueString);
}
I can't seem to use typeof(T).Name ?
Regards.
Attribute arguments must be types or compile-time constants. You can't call a method (the Name property getter) to supply a value to an attribute.
Unfortunately, BindAttribute is consumed by the MVC innards in long-ish hard-coded call chain without a trivial extension hook. If you want to add a similar attribute that would allow inference of the prefix, this is possible, but you would pretty much need to replace the ControllerActionInvoker merely in order to change the parameter binding behaviour.

Few questions... ModelState.IsValid and Grouped CheckBox Values

Using ASP.NET MVC when I create my model, then a controller based on the model with CRUD operations, the CRUD views are generated. I added some code using Fluent API to require certain fields but for some reason the ModelState.IsValid passes even when these fields are not completed. What determines whether this passes or not? I thought it was based on your model property data types and other things like being required or maxlength, etc....
Also, I have manually added code to grab a list of Categories from the database and generate a checkbox for each one in the View. This is a navigation property for the Project model where there is a many-many relationship. To get the group of checked values in the Create(Project project) method in the controller I use:
var selected = Request["categories"].Split(',');
This however, throws the classic Object reference not set to an instance of an object error if no values are checked. So what I want to know is, how can I determine that this does not have any values so I can do something else once detected?
I added some code using Fluent API to require certain fields but for
some reason the ModelState.IsValid passes even when these fields are
not completed.
ASP.NET MVC doesn't know anything about the Fluent API of Entity Framework and doesn't evaluate this configuration. You only can use the data annotations which MVC will recognize:
[Required]
public string SomeProperty { get; set; }
...how can I determine that this does not have any values so I can do
something else once detected?
Not sure if I understand it correctly but I'd say:
var categories = Request["categories"];
if (categories != null)
{
var selected = categories.Split(',');
// ...
}
else
{
// do something else
}

MVC 3 - Html.EditorFor seems to cache old values after $.ajax call

This is a follow on from the following question:
MVC 3 + $.ajax - response seems to be caching output from partial view
There is a detailed description of the problem over there. However, I have now managed to narrow down the problem, that seems to be with the Html.EditorFor helpers, hence the new question.
The issue:
I post data to the server using $.ajax, then return the html of the partial view that holds the input controls. The problem is that, despite passing a newly created object to the Partial Views model, the various #Html.EditorFor and #Html.DropDownListFor helpers return the OLD DATA!.
I can prove that the model has correctly passed in a new object to the helpers, by printing the value out beside the Html helper. Ie:
#Html.EditorFor(model => model.Transaction.TransactionDate)
#Model.Transaction.TransactionDate.ToString()
As the following image shows, the #Html.EditorFor is returning the wrong data:
[Note that the value beside the Comentario text box is a date time, because I was testing replacing the default values with a value that would change with each post, ie, a DateTime.]
If I replace the #Html.EditorFor for TransactionDate with a plain old #Html.TextBox():
#Html.TextBox("Transaction_TransactionDate", Model.Transaction.TransactionDate)
Then it renders the correct TransactionDate value for a new Transaction object, ie, DateTime.MinValue (01/01/0001...).
Therefore...
The problem is with the #Html.EditorFor helpers. The problem also happens with TextBoxFor and DropDownListFor.
The problem being that these helpers seem to cache the old value.
What am I doing wrong??!
EDIT:
I have just tried debugging in the custom Editor template for dates, and in there, ViewData.TemplateInfo.FormattedModelValue shows the correct value, ie, "01/01/0001". However, once it gets to Fiddler, the response is showing the old date, eg, "01/09/2011" in the image above.
As a result, I just think that there is some caching going on here, but I have none set up, so nothing makes any sense.
There is no caching involved here. It's just how HTML helper work. They first look at the ModelState when binding their values and then in the model. So if you intend to modify any of the POSTed values inside your controller action make sure you remove them from the model state first:
[HttpPost]
public virtual ActionResult AjaxCreate(Transaction transaction)
{
if (ModelState.IsValid)
{
service.InsertOrUpdate(transaction);
service.Save();
}
service.ChosenCostCentreId = transaction.IdCostCentre;
TransactionViewModel viewModel = new TransactionViewModel();
ModelState.Remove("Transaction");
viewModel.Transaction = new Transaction();
ModelState.Remove("CostCentre");
viewModel.CostCentre = service.ChosenCostCentre;
...
return PartialView("_Create", viewModel);
}
Even if you do not specify caching, it sometimes can occur. For my controllers which handle AJAX and JSON requests, I decorate them as follows:
[OutputCache(Location = OutputCacheLocation.None, NoStore = true)]
This specifically declares no caching should occur.
UPDATE
Based on an answer Darin Dimitrov gave here, try adding the following line to your controller action:
ModelState.Clear();
i have never seen this but basically if you are using ajax to request this data, you need to set nochache: i am assuming you using jQuery.ajax here so will show the code:
$.ajax({
url: "somecontroller/someAction,
cache: false, // this is key to make sure JQUERY does not cache your request
success: function( data ) {
alert( data );
}
});
just a stab in the dark, i assume you have probably already covered this already. have you tried to create a new model first and then populate that new instance of the model with your data, and then send this to your view!
Finally not sure what DB server your using but have you check to see that DB results are not cached and that you are not just requesting SQL results from the DB cache... i dont use MsSQL but i hear that it has outputCaching until something is change on the DB server itself?? anyway just a few thoughts
This was unexpected behavior for me, and although I understand the reason why it's necessary to give ModelState precedence, I needed a way to remove that entry so that the value from Model is used instead.
Here are a couple methods I came up with to assist with this. The RemoveStateFor method will take a ModelStateDictionary, a Model, and an expression for the desired property, and remove it.
HiddenForModel can be used in your View to create a hidden input field using only the value from the Model, by first removing its ModelState entry. (This could easily be expanded for the other helper extension methods).
/// <summary>
/// Returns a hidden input field for the specified property. The corresponding value will first be removed from
/// the ModelState to ensure that the current Model value is shown.
/// </summary>
public static MvcHtmlString HiddenForModel<TModel, TProperty>(this HtmlHelper<TModel> helper,
Expression<Func<TModel, TProperty>> expression)
{
RemoveStateFor(helper.ViewData.ModelState, helper.ViewData.Model, expression);
return helper.HiddenFor(expression);
}
/// <summary>
/// Removes the ModelState entry corresponding to the specified property on the model. Call this when changing
/// Model values on the server after a postback, to prevent ModelState entries from taking precedence.
/// </summary>
public static void RemoveStateFor<TModel, TProperty>(this ModelStateDictionary modelState, TModel model,
Expression<Func<TModel, TProperty>> expression)
{
var key = ExpressionHelper.GetExpressionText(expression);
modelState.Remove(key);
}
Call from a controller like this:
ModelState.RemoveStateFor(model, m => m.MySubProperty.MySubValue);
or from a view like this:
#Html.HiddenForModel(m => m.MySubProperty.MySubValue)
It uses System.Web.Mvc.ExpressionHelper to get the name of the ModelState property. This is especially useful when you have "Nested" models since the key name isn't obvious.
Make sure you're not doing this:
#Html.EditorFor(model => model.Transaction.TransactionDate.Date)
I did this, and the model never got the value back. It worked perfectly once I remove the .Date.

Resources