I have the following repository method:-
public async Task<Skill> FindSkill(int id, params Expression<Func<Skill, object>>[] includeProperties)
{
var query = context.Skills.AsQueryable();
if (includeProperties != null )
query = includeProperties.Aggregate(query, (current, include) => current.Include(include));
return await query.SingleOrDefaultAsync(a => a.SkillID == id);
}
and I call this method as follow:-
public async Task<ActionResult> Deactivate(int id, Byte[] timestamp = null)
{
var skill = await unitofwork.SkillRepository.FindSkill(id);
//snip
}
but I can not detect when I am not passing any params Expression, I tried also the following checks but did not work:-
includeProperties.Count() != 0 || includeProperties[0].Name == "0"
now I have noted that inside VS the following will be received when passing empty list:-
When using params and not supplying any parameters for it. An empty array will be created. You should use includeProperties.Length !=0 as Nick Bailey suggests in his comment.
Related
I have an MVC5 application that has a method populates and returns a partial view. Since the method accepts an ID as a parameter, Id like to return an error if it is not supplied.
[HttpGet] public PartialViewResult GetMyData(int? id)
{
if (id == null || id == 0)
{
// I'd like to return an invalid code here, but this must be of type "PartialViewResult"
return new HttpStatusCodeResult(HttpStatusCode.BadRequest); // Does not compile
}
var response = MyService.GetMyData(id.Value);
var viewModel = Mapper.Map<MyData, MyDataViewModel>(response.Value);
return PartialView("~/Views/Data/_MyData.cshtml", viewModel);
}
What is the proper way to report an error for a method that returns a PartialViewResult as its output?
You could create a friendly error partial and do the following:
[HttpGet]
public PartialViewResult GetMyData(int? id)
{
if (id == null || id == 0)
{
// I'd like to return an invalid code here, but this must be of type "PartialViewResult"
return PartialView("_FriendlyError");
}
var response = MyService.GetMyData(id.Value);
var viewModel = Mapper.Map<MyData, MyDataViewModel>(response.Value);
return PartialView("~/Views/Data/_MyData.cshtml", viewModel);
}
This way there is a better user experience rather than just throwing them anything. You can customise that error partial to include some details that they did wrong etc.
You can use manual Exception
if (id == null || id == 0)
{
throw new Exception("id must have value");
}
if you work with ajax, you can handle error by error callback function
$.ajax({
type: 'POST',
url: '/yourUrl',
success: function (response) {
// call here when successfully // 200
},
error: function (e) {
// handle error in here
}
})
While you cannot return HttpStatusCodeResult as a PartialViewResult to set the response status code for you, you can certainly still do the manual labour yourself to achieve the same result.
Using the OP's example:
[HttpGet]
public PartialViewResult GetMyData(int? id)
{
if (id == null || id == 0)
{
// Return an invalid code here
HttpContext.Response.StatusCode = HttpStatusCode.BadRequest;
// Optionally set the description as well
HttpContext.Response.StatusDescription = "Bad Request";
// Return null as it doesn't matter anymore
return null;
}
var response = MyService.GetMyData(id.Value);
var viewModel = Mapper.Map<MyData, MyDataViewModel>(response.Value);
return PartialView("~/Views/Data/_MyData.cshtml", viewModel);
}
Success, by setting the status code directly on HttpContext.Response which is accessible from the base class Controller, and what HttpStatusCodeResult would've done anyway.
nJoy!
I am working on an asp.net mvc5 web application , with EF-6. I am trying to dynamically pass .Include & .Select as follow:-
var query = context.SecurityRoles.AsQueryable();
foreach (var include in includeProperties.Split(new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries))
{
query = query.Include(include);
}
return await query
.SingleOrDefaultAsync(a2 => a2.SecurityRoleID == id);
}
which i will be calling as follow:-
var securityrole = await uniteofwork.SecurityRoleRepository
.FindSecurityRole(id.Value, "SecurityRoleModulePermissions.Select(a2=>a2.Module),SecurityRoleModulePermissions.Select(a2=>a2.PermissionLevel)),Staffs");
But the i am getting the following exception:-
A specified Include path is not valid. The EntityType
'SkillManagementModel.SecurityRoleModulePermission' does not declare a
navigation property with the name 'Select(a2=>a2'
You can include your navigation properties dynamically like this:
public async Task<SecurityRole> FindSecurityRole(Expression<Func<SecurityRole, bool>> predicate, params Expression<Func<SecurityRole, object>>[] includeProperties)
{
var query = context.SecurityRoles.AsQueryable();
if (includeProperties != null)
query = includeProperties.Aggregate(query, (current, include) => current.Include(include));
return await query.SingleOrDefaultAsync(predicate);
}
var securityrole = await uniteofwork.SecurityRoleRepository.FindSecurityRole
(sr => sr.Id == id.Value, sr => sr.Module, sr =>sr.PermissionLevel)
I am currently trying to write a custom authentication filter and I need to access the dto that is being passed as a parameter to the action in my filter. Lets say I have an action like this
[AuthenticateProfile]
public ActionResult EditProfile(ProfileDTO profileDto)
{
if (ModelState.IsValid)
{
// Do crazy stuff
}
return something....
}
I need to do my authentication based on some of the properties that are inside profiledto object.
I want to know how I can get this object inside my filter from AuthorizationContext.
Here's how I did it:
var parameters = filterContext.ActionDescriptor.GetParameters();
var values = parameters.Select(s => new
{
Name = s.ParameterName,
Value = filterContext.HttpContext.Request[s.ParameterName]
});
Assuming that your logic is happening in OnActionExecuting (meaning before the actual controller action is run), then one way of doing this (as outlined here) would be:
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
if (!filterContext.Controller.ViewData.ModelState.IsValid)
return;
var profileDto = filterContext.ActionParameters.SingleOrDefault(ap => ap.Key == "profileDto").Value;
if (profileDto != null)
{
// do something with profileDto
}
}
ASP.NET Core
var parameters =
filterContext
.ActionDescriptor
.Parameters
.Select(s => new
{
Name = s.Name,
Value = context.HttpContext.Request.Query[s.Name]
});
extension
public static StringValues? Parameter(this AuthorizationFilterContext context, string name)
{
var parameter = context.ActionDescriptor.Parameters.FirstOrDefault(it => it.Name == name);
if (parameter == null) return null;
return context.HttpContext.Request.Query[parameter.Name];
}
use
var parameter = context.Parameter("id");
Here are some examples for you to try out:
public class AuthenticateProfileAttribute : AuthorizeAttribute {
public override void OnAuthorization(AuthorizationContext filterContext) {
// Looping through named parameters
foreach (string name in filterContext.HttpContext.Request.QueryString.AllKeys) {
var value = filterContext.HttpContext.Request.QueryString[name];
// do something with the iteration
Console.WriteLine(name + ": " + value);
}
// Looping through un-named parameters a.k.a route parameters
foreach (KeyValuePair<string, object> parameter in filterContext.RouteData.Values) {
var name = parameter.Key;
var value = parameter.Value;
// do something with the iteration
Console.WriteLine(name + ": " + value);
}
// Get single named parameter
string parameter = filterContext.HttpContext.Request.QueryString["parameter"];
// Get single route parameter
parameter = filterContext.RouteData.Values["parameter"].ToString();
}
}
Using MVC 5.
It's good day today! But... :)
I have the following problem: I have a controller that updates a type_text field in a Mysql DB. The user types text in texarea, clicks "Update" and, oh magic, the text is posted to the database. But without a break...
In the controller i have:
[Authorize]
[HttpPost]
public string EditComment(FormCollection formValues)
{
var Commenter = User.Identity.Name;
Int64 id = Convert.ToInt64(Request.Form["id"]);
string formValue = Request.Form["value"];
formValue = formValue.Replace("\r\n", "<br/>").Replace("\r", "<br/>");
comments updateCommnets = db.comments.SingleOrDefault(d => d.id == id && d.commenterName == Commenter);
updateCommnets.comment = formValue;
db.SaveChanges();
return formValue;
}
It's making me crazy for 2 days...
Can Somebody help me? Thanks a lot!
UPDATED
I use jeditable to perform inline editing. Example of post string: value=Some+text%0ASome2+text2
I would store the text as is in the database without converting \r\n to <br/>:
[Authorize]
[HttpPost]
public ActionResult EditComment(string value, long id)
{
var commenter = User.Identity.Name;
var updateCommnets = db.comments.SingleOrDefault(d => d.id == id && d.commenterName == commenter);
updateCommnets.comment = value;
db.SaveChanges();
return Content(value, "text/plain");
}
Then I would write a custom HTML helper to format the values in the view if necessary to show those comments.
public static MvcHtmlString FormatComment(this HtmlHelper html, string comment)
{
if (string.IsNullOrEmpty(comment))
{
return MvcHtmlString.Empty;
}
var lines = comment
.Split(
new string[] { Environment.NewLine },
StringSplitOptions.None
)
.Select(x => HttpUtility.HtmlEncode(x))
.ToArray();
return MvcHtmlString.Create(string.Join("<br/>", lines));
}
and then in the view:
#Html.FormatComment(Model.Comment)
Do not convert the text that is sent to the database.
Use:
#MvcHtmlString.Create(value)
Here's the manual
I have a method that returns an array (string[]) and I'm trying to pass this array of strings into an Action Link so that it will create a query string similar to:
/Controller/Action?str=val1&str=val2&str=val3...etc
But when I pass new { str = GetStringArray() } I get the following url:
/Controller/Action?str=System.String%5B%5D
So basically it's taking my string[] and running .ToString() on it to get the value.
Any ideas? Thanks!
Try creating a RouteValueDictionary holding your values. You'll have to give each entry a different key.
<% var rv = new RouteValueDictionary();
var strings = GetStringArray();
for (int i = 0; i < strings.Length; ++i)
{
rv["str[" + i + "]"] = strings[i];
}
%>
<%= Html.ActionLink( "Link", "Action", "Controller", rv, null ) %>
will give you a link like
<a href='/Controller/Action?str=val0&str=val1&...'>Link</a>
EDIT: MVC2 changed the ValueProvider interface to make my original answer obsolete. You should use a model with an array of strings as a property.
public class Model
{
public string Str[] { get; set; }
}
Then the model binder will populate your model with the values that you pass in the URL.
public ActionResult Action( Model model )
{
var str0 = model.Str[0];
}
This really annoyed me so with inspiration from Scott Hanselman I wrote the following (fluent) extension method:
public static RedirectToRouteResult WithRouteValue(
this RedirectToRouteResult result,
string key,
object value)
{
if (value == null)
throw new ArgumentException("value cannot be null");
result.RouteValues.Add(key, value);
return result;
}
public static RedirectToRouteResult WithRouteValue<T>(
this RedirectToRouteResult result,
string key,
IEnumerable<T> values)
{
if (result.RouteValues.Keys.Any(k => k.StartsWith(key + "[")))
throw new ArgumentException("Key already exists in collection");
if (values == null)
throw new ArgumentNullException("values cannot be null");
var valuesList = values.ToList();
for (int i = 0; i < valuesList.Count; i++)
{
result.RouteValues.Add(String.Format("{0}[{1}]", key, i), valuesList[i]);
}
return result;
}
Call like so:
return this.RedirectToAction("Index", "Home")
.WithRouteValue("id", 1)
.WithRouteValue("list", new[] { 1, 2, 3 });
Another solution that just came to my mind:
string url = "/Controller/Action?iVal=5&str=" + string.Join("&str=", strArray);
This is dirty and you should test it before using it, but it should work nevertheless. Hope this helps.
There is a library called Unbinder, which you can use to insert complex objects into routes/urls.
It works like this:
using Unbound;
Unbinder u = new Unbinder();
string url = Url.RouteUrl("routeName", new RouteValueDictionary(u.Unbind(YourComplexObject)));
This is a HelperExtension solving array and IEnumerable properties troubles :
public static class AjaxHelperExtensions
{
public static MvcHtmlString ActionLinkWithCollectionModel(this AjaxHelper ajaxHelper, string linkText, string actionName, object model, AjaxOptions ajaxOptions, IDictionary<string, object> htmlAttributes)
{
var rv = new RouteValueDictionary();
foreach (var property in model.GetType().GetProperties())
{
if (typeof(ICollection).IsAssignableFrom(property.PropertyType))
{
var s = ((IEnumerable<object>)property.GetValue(model));
if (s != null && s.Any())
{
var values = s.Select(p => p.ToString()).Where(p => !string.IsNullOrEmpty(p)).ToList();
for (var i = 0; i < values.Count(); i++)
rv.Add(string.Concat(property.Name, "[", i, "]"), values[i]);
}
}
else
{
var value = property.GetGetMethod().Invoke(model, null) == null ? "" : property.GetGetMethod().Invoke(model, null).ToString();
if (!string.IsNullOrEmpty(value))
rv.Add(property.Name, value);
}
}
return System.Web.Mvc.Ajax.AjaxExtensions.ActionLink(ajaxHelper, linkText, actionName, rv, ajaxOptions, htmlAttributes);
}
}
I'd use POST for an array. Aside from being ugly and an abuse of GET, you risk running out of URL space (believe it or not).
Assuming a 2000 byte limit. The query string overhead (&str=) reduces you to ~300 bytes of actual data (assuming the rest of the url is 0 bytes).