How do I send deep JSON objects to an Action? - asp.net-mvc

In my web app I have a dynamically generated form that I use to create a JSON object to pitch back to an Action. As seen here:
function getConfigItemWithValidators() {
log.info("getConfigItemWithValidators()");
var oConfigItem = {
"Name": $("#txtName").html(),
"InputFileCellIndex": $("#inpFieldIndex").val(),
"Validators": new Array() };
for (var i = 0; true; i++) {
var oHiddenValidatorName = $("[name=hidVld"+i+"]");
var oHiddenValidatorVal = $("[name=txtVld"+i+"]");
if ($("[name=hidVld" + i + "]").length > 0) {
var oValidator = {
"ValidationType": oHiddenValidatorName.val(),
"ValidationValue": oHiddenValidatorVal.val() };
oConfigItem.Validators.push(oValidator);
}
else
break;
}
return oConfigItem
}
function saveConfigItemChanges() {
log.info("saveConfigItemChanges()");
var oConfigItem = getConfigItemWithValidators();
$("#divRulesContainer").hide("normal");
$.getJSON("PutValidationRules", oConfigItem,
saveConfigItemChangesCallback);
}
In my action, while debugging, I notice that model.Validators is empty:
[AcceptVerbs(HttpVerbs.Get)]
public JsonResult PutValidationRules(ConfigItem model)
{
// model.Validators is empty
return Json(true);
}
Here is the code to ConfigItem:
public class ConfigItem
{
public string Name { get; set; }
public int InputFileCellIndex { get; set; }
private IList<Validator> _validators = new List<Validator>();
public IList<Validator> Validators
{
get
{
return _validators;
}
}
public void AddValidator(Validator aValidator)
{
aValidator.ConfigItem = this;
_validators.Add(aValidator);
}
}
Is there something I need to do to get ConfigItem.Validators to get built for my JSON requests?

It is empty because default binder does not work for arrays very well. You will need to implement a custombinder.
You can see here an example of custombinders

Related

BreezeSharp Attach Property key not found

I'm implementing an application with Breezesharp. I ran into a issue when insert the entity in the EntityManager. The error is:
There are no KeyProperties yet defined on EntityType: 'TransportReceipt:#Business.DomainModels'
I already faced this error with my first entity type "Customer" and implement a mismatching approach as suggested here. In that case I made the get operation against my WebApi with success. But now I'm creating the TransportReceipt entity inside my application.
Mapping mismatch fix
public static class ExtendMap
{
private static bool? executed;
public static void Execute(MetadataStore metadataStore) {
if (ExtendMap.executed == true)
{
return;
}
var customerBuilder = new EntityTypeBuilder<Customer>(metadataStore);
customerBuilder.DataProperty(t => t.id).IsPartOfKey().IsAutoIncrementing();
var transportReceiptBuilder = new EntityTypeBuilder<TransportReceipt>(metadataStore);
transportReceiptBuilder.DataProperty(t => t.id).IsPartOfKey().IsAutoIncrementing();
var transportReceiptAttachmentBuilder = new EntityTypeBuilder<TransportReceiptAttachment>(metadataStore);
transportReceiptAttachmentBuilder.DataProperty(t => t.id).IsPartOfKey().IsAutoIncrementing();
var uploadedFileBuilder = new EntityTypeBuilder<UploadedFile>(metadataStore);
uploadedFileBuilder.DataProperty(t => t.id).IsPartOfKey().IsAutoIncrementing();
ExtendMap.executed = true;
}
}
My base dataservice core code
public abstract class SimpleBaseDataService
{
public static string Metadata { get; protected set; }
public static MetadataStore MetadataStore { get; protected set; }
public string EntityName { get; protected set; }
public string EntityResourceName { get; protected set; }
public EntityManager EntityManager { get; set; }
public string DefaultTargetMethod { get; protected set; }
static SimpleBaseDataService()
{
try
{
var metadata = GetMetadata();
metadata.Wait();
Metadata = metadata.Result;
MetadataStore = BuildMetadataStore();
}
catch (Exception ex)
{
var b = 0;
}
}
public SimpleBaseDataService(Type entityType, string resourceName, string targetMethod = null)
{
var modelType = typeof(Customer);
Configuration.Instance.ProbeAssemblies(ConstantsFactory.BusinessAssembly);
try
{
this.EntityName = entityType.FullName;
this.EntityResourceName = resourceName;
this.DefaultTargetMethod = (string.IsNullOrWhiteSpace(targetMethod) ? "GetAllMobile" : targetMethod);
var dataService = new DataService($"{ConstantsFactory.Get.BreezeHostUrl}{this.EntityResourceName}", new CustomHttpClient());
dataService.HasServerMetadata = false;
this.EntityManager = new EntityManager(dataService, SimpleBaseDataService.MetadataStore);
this.EntityManager.MetadataStore.AllowedMetadataMismatchTypes = MetadataMismatchTypes.AllAllowable;
// Attach an anonymous handler to the MetadataMismatch event
this.EntityManager.MetadataStore.MetadataMismatch += (s, e) =>
{
// Log the mismatch
var message = string.Format("{0} : Type = {1}, Property = {2}, Allow = {3}",
e.MetadataMismatchType, e.StructuralTypeName, e.PropertyName, e.Allow);
// Disallow missing navigation properties on the TodoItem entity type
if (e.MetadataMismatchType == MetadataMismatchTypes.MissingCLRNavigationProperty &&
e.StructuralTypeName.StartsWith("TodoItem"))
{
e.Allow = false;
}
};
}
catch (Exception ex)
{
var b = 0;
}
}
}
This is who I'm trying to add the new entity
//DataService snippet
public void AttachEntity(T entity)
{
this.EntityManager.AttachEntity(entity, EntityState.Added);
}
//Business
this.TransportReceipt = new TransportReceipt { id = Guid.NewGuid(), date = DateTime.Now, customerId = Customer.id/*, customer = this.Customer*/ };
this.Attachments = new List<TransportReceiptAttachment>();
this.TransportReceipt.attachments = this.Attachments;
TransportReceiptDataService.AttachEntity(this.TransportReceipt);
When I try to add add the entity to the EntityManager, I can see the custom mapping for all my entity classes.
So my question is what I'm doing wrong.
Ok. That was weird.
I changed the mapping for a new fake int property and works. I'll test the entire save flow soon and I'll share the result here.
Update
I moved on and start removing Breezesharp. The Breezesharp project is no up-to-date and doesn't have good integration with Xamarin. I'll appreciate any comment with your experience.

How to return callback json from VC Controller?

I am using 3rd party Kendo UI in client side. it expects data from server in below format.
callback([{"TaskID":4,"OwnerID":2,"Title":"Bowling}])
I am having below code in server side
public JsonResult GetAllAppointments()
{
IEnumerable<AppointmentModel> appointmentCollection = app_repository.GetAll();
if (appointmentCollection == null)
{
return Json(appointmentCollection, JsonRequestBehavior.AllowGet);
}
return Json(appointmentCollection, JsonRequestBehavior.AllowGet);
}
But this returns just json, how to add "callback" to it?
Ideally you should get an interceptor at client side which should let you modify your response format..but since you have not provided any further details I won't be able to comment on that.
You can change your return type fron JsonResult to string and manually serialize your response.You can use Json.NET to serialize your response.
Here is a NuGet LINK for the same.
public class AppointmentModel
{
public string TaskID { get; set; }
public string OwnerID { get; set; }
public string Title { get; set; }
}
public string GetAllAppointments()
{
string responseFormat = #"callback({0})";
IEnumerable<AppointmentModel> appointmentCollection = getDummyAppoitments();
if (appointmentCollection != null)
{
string json_string = Newtonsoft.Json.JsonConvert.SerializeObject(appointmentCollection);
return string.Format(responseFormat, json_string);;
}
//no items were present so sending empty response;
return string.Format(responseFormat, "[]");;
}
private IEnumerable<AppointmentModel> getDummyAppoitments()
{
return new List<AppointmentModel>() {
new AppointmentModel()
{
TaskID = "4",
OwnerID = "2",
Title = "Bowling"
}
};
}
Check out response.
Cheers!!

Get custom attribute for parameter when model binding

I've seen a lot of similar posts on this, but haven't found the answer specific to controller parameters.
I've written a custom attribute called AliasAttribute that allows me to define aliases for parameters during model binding. So for example if I have: public JsonResult EmailCheck(string email) on the server and I want the email parameter to be bound to fields named PrimaryEmail or SomeCrazyEmail I can "map" this using the aliasattribute like this: public JsonResult EmailCheck([Alias(Suffix = "Email")]string email).
The problem: In my custom model binder I can't get a hold of the AliasAttribute class applied to the email parameter. It always returns null.
I've seen what the DefaultModelBinder class is doing to get the BindAttribute in reflector and its the same but doesn't work for me.
Question: How do I get this attribute during binding?
AliasModelBinder:
public class AliasModelBinder : DefaultModelBinder
{
public static ICustomTypeDescriptor GetTypeDescriptor(Type type)
{
return new AssociatedMetadataTypeTypeDescriptionProvider(type).GetTypeDescriptor(type);
}
public override object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
{
var value = base.BindModel(controllerContext, bindingContext);
var descriptor = GetTypeDescriptor(bindingContext.ModelType);
/*************************/
// this next statement returns null!
/*************************/
AliasAttribute attr = (AliasAttribute)descriptor.GetAttributes()[typeof(AliasAttribute)];
if (attr == null)
return null;
HttpRequestBase request = controllerContext.HttpContext.Request;
foreach (var key in request.Form.AllKeys)
{
if (string.IsNullOrEmpty(attr.Prefix) == false)
{
if (key.StartsWith(attr.Prefix, StringComparison.InvariantCultureIgnoreCase))
{
if (string.IsNullOrEmpty(attr.Suffix) == false)
{
if (key.EndsWith(attr.Suffix, StringComparison.InvariantCultureIgnoreCase))
{
return request.Form.Get(key);
}
}
return request.Form.Get(key);
}
}
else if (string.IsNullOrEmpty(attr.Suffix) == false)
{
if (key.EndsWith(attr.Suffix, StringComparison.InvariantCultureIgnoreCase))
{
return request.Form.Get(key);
}
}
if (attr.HasIncludes)
{
foreach (var include in attr.InlcludeSplit)
{
if (key.Equals(include, StringComparison.InvariantCultureIgnoreCase))
{
return request.Form.Get(include);
}
}
}
}
return null;
}
}
AliasAttribute:
[AttributeUsage(AttributeTargets.Parameter | AttributeTargets.Class, AllowMultiple = false, Inherited = true)]
public class AliasAttribute : Attribute
{
private string _include;
private string[] _inlcludeSplit = new string[0];
public string Prefix { get; set; }
public string Suffix { get; set; }
public string Include
{
get
{
return _include;
}
set
{
_include = value;
_inlcludeSplit = SplitString(_include);
}
}
public string[] InlcludeSplit
{
get
{
return _inlcludeSplit;
}
}
public bool HasIncludes { get { return InlcludeSplit.Length > 0; } }
internal static string[] SplitString(string original)
{
if (string.IsNullOrEmpty(original))
{
return new string[0];
}
return (from piece in original.Split(new char[] { ',' })
let trimmed = piece.Trim()
where !string.IsNullOrEmpty(trimmed)
select trimmed).ToArray<string>();
}
}
Usage:
public JsonResult EmailCheck([ModelBinder(typeof(AliasModelBinder)), Alias(Suffix = "Email")]string email)
{
// email will be assigned to any field suffixed with "Email". e.g. PrimaryEmail, SecondaryEmail and so on
}
Gave up on this and then stumbled across the Action Parameter Alias code base that will probably allow me to do this. It's not as flexible as what I started out to write but probably can be modified to allow wild cards.
what I did was make my attribute subclass System.Web.Mvc.CustomModelBinderAttribute which then allows you to return a version of your custom model binder modified with the aliases.
example:
public class AliasAttribute : System.Web.Mvc.CustomModelBinderAttribute
{
public AliasAttribute()
{
}
public AliasAttribute( string alias )
{
Alias = alias;
}
public string Alias { get; set; }
public override IModelBinder GetBinder()
{
var binder = new AliasModelBinder();
if ( !string.IsNullOrEmpty( Alias ) )
binder.Alias = Alias;
return binder;
}
}
which then allows this usage:
public ActionResult Edit( [Alias( "somethingElse" )] string email )
{
// ...
}

uCommerce - add dynamic property to order line

I have hit a problem building a uCommerce site based on top of the demo razor store available http://thesitedoctor.co.uk/portfolio/avenue-clothingcom/
The demo uses servicestack and the ucommerceapi for its basket functions.
I am trying to add a dynamic property to the basket (on an order line) at the point where the user clicks buy. I traced through the productpage.js file and amended the code to add a new property ('message'):
function (data) {
var variant = data.Variant;
$.uCommerce.addToBasket(
{
sku: variant.Sku,
variantSku: variant.VariantSku,
quantity: qty,
message: $('#personalisedMessage').val()
},
function () {
updateCartTotals(addToCartButton);
}
);
});
using firebug, i checked the data that is being posted
addToExistingLine: true
message: "this is a message"
quantity:"1"
sku: "Product (options: none)"
variantSku:""
Posting this does not cause an error, but I cannot tell if it has worked either - I cannot find it in the database, assuming that it would be stored in OrderProperty table. In this scenario, I am 'buying' a product with no variations.
Any help is greatly appreciated with this.
Out of the box you can't add order/line item properties via the API like that. The API payload that you've added to is specified although valid JSON won't get interpreted/used by the API.
Instead what you'll need to do is add your own method to the API. To do this you'll need to implement a service from IUCommerceApiService and then you can do what you need. I've created an example (untested) below and will get it added to the demo store as I think it's a useful bit of functionality to have.
public class AddOrderLineProperty
{
public int? OrderLineId { get; set; }
public string Sku { get; set; }
public string VariantSku { get; set; }
public string Key { get; set; }
public string Value { get; set; }
}
public class AddOrderLinePropertyResponse : IHasResponseStatus
{
public AddOrderLinePropertyResponse() { }
public AddOrderLinePropertyResponse(UCommerce.EntitiesV2.OrderLine line)
{
if (line == null)
{
UpdatedLine = new LineItem();
return;
}
var currency = SiteContext.Current.CatalogContext.CurrentCatalog.PriceGroup.Currency;
var lineTotal = new Money(line.Total.Value, currency);
UpdatedLine = new LineItem()
{
OrderLineId = line.OrderLineId,
Quantity = line.Quantity,
Sku = line.Sku,
VariantSku = line.VariantSku,
Price = line.Price,
ProductName = line.ProductName,
Total = line.Total,
FormattedTotal = lineTotal.ToString(),
UnitDiscount = line.UnitDiscount,
VAT = line.VAT,
VATRate = line.VATRate
};
}
public ResponseStatus ResponseStatus { get; set; }
public LineItem UpdatedLine { get; set; }
}
public class AddOrderLinePropertyService : ServiceBase<AddOrderLineProperty>, IUCommerceApiService
{
protected override object Run(AddOrderLineProperty request)
{
var orderLineId = request.OrderLineId;
var sku = request.Sku;
var variantSku = request.VariantSku;
var orderLine = findOrderLine(orderLineId, sku, variantSku);
addPropertyToOrderLine(orderLine, request.Key, request.Value);
TransactionLibrary.ExecuteBasketPipeline();
var newLine = findOrderLine(orderLineId, sku, variantSku);
return new AddOrderLinePropertyResponse(newLine);
}
private void addPropertyToOrderLine(OrderLine orderLine, string key, string value)
{
if (orderLine == null)
return;
orderLine[key] = value;
orderLine.Save();
}
private static OrderLine findOrderLine(int? orderLineId, string sku, string variantSku)
{
return orderLineId.HasValue
? getOrderLineByOrderLineId(orderLineId)
: getOrderLineBySku(sku, variantSku);
}
private static OrderLine getOrderLineBySku(string sku, string variantSku)
{
return String.IsNullOrWhiteSpace(variantSku)
? getOrderLines().FirstOrDefault(l => (l.Sku == sku))
: getOrderLines().FirstOrDefault(l => (l.Sku == sku && l.VariantSku == variantSku));
}
private static OrderLine getOrderLineByOrderLineId(int? orderLineId)
{
return getOrderLines().FirstOrDefault(l => l.OrderLineId == orderLineId);
}
private static ICollection<OrderLine> getOrderLines()
{
return TransactionLibrary.GetBasket().PurchaseOrder.OrderLines;
}
}
You'll need to add the new method to uCommerce.jQuery.js as well something like this:
addOrderLineProperty: function (options, onSuccess, onError) {
var defaults = {
orderLineId: 0
};
var extendedOptions = $.extend(defaults, options);
callServiceStack({ AddOrderLineProperty: extendedOptions }, onSuccess, onError);
}
Let me know if you have any issues using it.
Tim

How to validate uploaded file in ASP.NET MVC?

I have a Create action that takes an entity object and a HttpPostedFileBase image. The image does not belong to the entity model.
I can save the entity object in the database and the file in disk, but I am not sure how to validate these business rules:
Image is required
Content type must be "image/png"
Must not exceed 1MB
A custom validation attribute is one way to go:
public class ValidateFileAttribute : RequiredAttribute
{
public override bool IsValid(object value)
{
var file = value as HttpPostedFileBase;
if (file == null)
{
return false;
}
if (file.ContentLength > 1 * 1024 * 1024)
{
return false;
}
try
{
using (var img = Image.FromStream(file.InputStream))
{
return img.RawFormat.Equals(ImageFormat.Png);
}
}
catch { }
return false;
}
}
and then apply on your model:
public class MyViewModel
{
[ValidateFile(ErrorMessage = "Please select a PNG image smaller than 1MB")]
public HttpPostedFileBase File { get; set; }
}
The controller might look like this:
public class HomeController : Controller
{
public ActionResult Index()
{
var model = new MyViewModel();
return View(model);
}
[HttpPost]
public ActionResult Index(MyViewModel model)
{
if (!ModelState.IsValid)
{
return View(model);
}
// The uploaded image corresponds to our business rules => process it
var fileName = Path.GetFileName(model.File.FileName);
var path = Path.Combine(Server.MapPath("~/App_Data"), fileName);
model.File.SaveAs(path);
return Content("Thanks for uploading", "text/plain");
}
}
and the view:
#model MyViewModel
#using (Html.BeginForm("Index", "Home", FormMethod.Post, new { enctype = "multipart/form-data" }))
{
#Html.LabelFor(x => x.File)
<input type="file" name="#Html.NameFor(x => x.File)" id="#Html.IdFor(x => x.File)" />
#Html.ValidationMessageFor(x => x.File)
<input type="submit" value="upload" />
}
Based on Darin Dimitrov's answer which I have found very helpful, I have an adapted version which allows checks for multiple file types, which is what I was initially looking for.
public override bool IsValid(object value)
{
bool isValid = false;
var file = value as HttpPostedFileBase;
if (file == null || file.ContentLength > 1 * 1024 * 1024)
{
return isValid;
}
if (IsFileTypeValid(file))
{
isValid = true;
}
return isValid;
}
private bool IsFileTypeValid(HttpPostedFileBase file)
{
bool isValid = false;
try
{
using (var img = Image.FromStream(file.InputStream))
{
if (IsOneOfValidFormats(img.RawFormat))
{
isValid = true;
}
}
}
catch
{
//Image is invalid
}
return isValid;
}
private bool IsOneOfValidFormats(ImageFormat rawFormat)
{
List<ImageFormat> formats = getValidFormats();
foreach (ImageFormat format in formats)
{
if(rawFormat.Equals(format))
{
return true;
}
}
return false;
}
private List<ImageFormat> getValidFormats()
{
List<ImageFormat> formats = new List<ImageFormat>();
formats.Add(ImageFormat.Png);
formats.Add(ImageFormat.Jpeg);
formats.Add(ImageFormat.Gif);
//add types here
return formats;
}
}
Here is a way to do it using viewmodel, take a look at whole code here
Asp.Net MVC file validation for size and type
Create a viewmodel as shown below with FileSize and FileTypes
public class ValidateFiles
{
[FileSize(10240)]
[FileTypes("doc,docx,xlsx")]
public HttpPostedFileBase File { get; set; }
}
Create custom attributes
public class FileSizeAttribute : ValidationAttribute
{
private readonly int _maxSize;
public FileSizeAttribute(int maxSize)
{
_maxSize = maxSize;
}
//.....
//.....
}
public class FileTypesAttribute : ValidationAttribute
{
private readonly List<string> _types;
public FileTypesAttribute(string types)
{
_types = types.Split(',').ToList();
}
//....
//...
}
And file length validation in asp.net core:
public async Task<IActionResult> MyAction()
{
var form = await Request.ReadFormAsync();
if (form.Files != null && form.Files.Count == 1)
{
var file = form.Files[0];
if (file.Length > 1 * 1024 * 1024)
{
ModelState.AddModelError(String.Empty, "Maximum file size is 1 Mb.");
}
}
// action code goes here
}
You may want to consider saving the image to database also:
using (MemoryStream mstream = new MemoryStream())
{
if (context.Request.Browser.Browser == "IE")
context.Request.Files[0].InputStream.CopyTo(mstream);
else
context.Request.InputStream.CopyTo(mstream);
if (ValidateIcon(mstream))
{
Icon icon = new Icon() { ImageData = mstream.ToArray(), MimeType = context.Request.ContentType };
this.iconRepository.SaveOrUpdate(icon);
}
}
I use this with NHibernate - entity defined:
public Icon(int id, byte[] imageData, string mimeType)
{
this.Id = id;
this.ImageData = imageData;
this.MimeType = mimeType;
}
public virtual byte[] ImageData { get; set; }
public virtual string MimeType { get; set; }
Then you can return the image as a FileContentResult:
public FileContentResult GetIcon(int? iconId)
{
try
{
if (!iconId.HasValue) return null;
Icon icon = this.iconRepository.Get(iconId.Value);
return File(icon.ImageData, icon.MimeType);
}
catch (Exception ex)
{
Log.ErrorFormat("ImageController: GetIcon Critical Error: {0}", ex);
return null;
}
}
Note that this is using ajax submit. Easier to access the data stream otherwise.

Resources