The following code is working just fine but I have a few questions realated to it's design.
BlogEntry.cs
public class BlogEntry : EntityBase
{
/// <summary>
/// Gets or sets the blog entry comments.
/// </summary>
public virtual ICollection<BlogEntryComment> BlogEntryComments { get; set; }
}
BlogEntryComment.cs
public class BlogEntryComment : EntityBase//, IValidatableObject
{
/// <summary>
/// Gets or sets the comment.
/// </summary>
[StringLength(2500)]
[Required(ErrorMessageResourceName = "Comment", ErrorMessageResourceType = typeof(Validation))]
[AllowHtml]
public string Comment { get; set; }
/// <summary>
/// Gets or sets the blog entry id.
/// </summary>
public Guid BlogEntryId { get; set; }
/// <summary>
/// Gets or sets the blog entry.
/// </summary>
public virtual BlogEntry BlogEntry { get; set; }
/// <summary>
/// Gets or sets the user id.
/// </summary>
public Guid UserId { get; set; }
/// <summary>
/// Gets or sets the author.
/// </summary>
public virtual User User { get; set; }
}
BlogController.cs
[HttpPost]
public virtual ActionResult PostComment(Guid id, BlogEntryComment blogEntryComment)
{
var blogEntry = this.BlogEntryService.GetById(id);
if (blogEntry == null)
{
if (Request.IsAjaxRequest())
return Json(new { success = false, message = "Blog entry not found" });
return new HttpNotFoundWithViewResult("NotFound");
}
var user = UserService.GetByUsername(User.Identity.Name);
if (user == null)
{
if (Request.IsAjaxRequest())
return Json(new { success = false, message = "Unknown user!" });
return new HttpUnauthorizedResult();
}
if (!ModelState.IsValid)
{
var errorModel = new BlogEntryDetail()
{
BlogEntry = blogEntry,
HideNewCommentsForm = false
};
if (this.Request.IsAjaxRequest())
{
return PartialView(MVC.Blog.Views._CommentsControl, errorModel);
}
else
{
errorModel.RelatedBlogEntries = this.BlogEntryService.GetRelatedBlogEntries(false, blogEntry, 3).ToArray();
return View(errorModel);
}
}
blogEntryComment.User = user;
blogEntryComment.BlogEntry = blogEntry;
this.BlogEntryCommentService.Add(blogEntryComment);
var model = new BlogEntryDetail()
{
BlogEntry = blogEntry,
HideNewCommentsForm = true
};
if (this.Request.IsAjaxRequest())
{
return PartialView(MVC.Blog.Views._CommentsControl, model);
}
else
{
model.RelatedBlogEntries = this.BlogEntryService.GetRelatedBlogEntries(false, blogEntry, 3).ToArray();
return View(model);
}
}
BlogEntryService.cs
public class BlogEntryService : GenericEntityService<BlogEntry>, IBlogEntryService
{
/// <summary>
/// Initializes a new instance of the <see cref="BlogEntryService"/> class.
/// </summary>
/// <param name="unitOfWork">The <see cref="IUnitOfWork"/>.</param>
public BlogEntryService(IUnitOfWork unitOfWork)
: base(unitOfWork.BlogEntries, unitOfWork)
{
}
GenericEntityService.cs
public abstract class GenericEntityService<T> : IGenericEntityService<T> where T : MVCBlog.Core.Entities.EntityBase
{
/// <summary>
/// Initializes a new instance of the <see cref="GenericEntityService<T>"/> class.
/// </summary>
/// <param name="repository">The <see cref="MVCBlog.Core.Repository.IRepository{T}"/>.</param>
/// <param name="unitOfWork">The <see cref="IUnitOfWork"/>.</param>
protected GenericEntityService(IRepository<T> repository, IUnitOfWork unitOfWork)
{
this.Repository = repository;
this.UnitOfWork = unitOfWork;
}
}
The comment should be added to the database with a method of BlogEntryService.AddComment(..) or with it's own BlogEntryCommentService.Add(..) as with current implementation?
I'm validating User and BlogEntry in the controller, this validation should be part of the service layer? for example [Service].AddComment(Guid blogEntryId, string username, string comment)
Any other idea to improve the desing or code?
Acording to SRP (Single Response Principal) it should be BlogEntryCommentService.Add(..).
I suppose, this is not servicelayer validation.
Related
I have a performance problem using code first and entity framework. I only want to read a simple mapping info which to up to 3 seconds.
// The enity
[Table("ResourceMappingEntity")]
public class ResourceMappingEntity
{
/// <summary>
/// Gets or sets the testing number.
/// </summary>
/// <value>
/// The testing number.
/// </value>
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public long Id { get; set; }
[Required]
[Column(TypeName = "varchar")]
[StringLength(80)]
public string LocationNameOrIp { get; set; }
[Required]
[Column(TypeName = "varchar")]
[StringLength(80)]
public string Resource { get; set; } //todo mb: noch nicht drin
[Column(TypeName = "varchar")]
[StringLength(80)]
public string App { get; set; } //todo mb: noch nicht drin
/// <summary>
/// Gets or sets the created at.
/// </summary>
/// <value>
/// The created at.
/// </value>
[Column(TypeName = "datetime2")]
public DateTime CreatedAt { get; set; }
}
//the query
using (var ctx = new OsmaTestingContextFactory().Create())
{
ctx.Configuration.ProxyCreationEnabled = false;
var two = DateTime.Now.Ticks / TimeSpan.TicksPerMillisecond;
var dif = two - one;
IEnumerable<string> res = await ctx.ResourceMappingEntityStorage.AsNoTracking()
.Where(
x => x.LocationNameOrIp == IPOrName)
.Select(y => y.Resource)
.ToListAsync();
var three = DateTime.Now.Ticks / TimeSpan.TicksPerMillisecond;
var dif2 = three - two;
if (res.Count() != 1)
{
throw new Exception("Can not find a specific resouce");
}
return res.First();
}
// I also set IsUnicode to false....
Any ideas?
Hope for help or alternative solutions?! Should be ok in a few milliseconds like in SQL managment studio
Is there a good or standrad way to test the performance of ef?
Thx
Marcus
I am having two datepickers fields on my form, and i would like to validate that the to date is greater than from date.
Is there any validation attribute in MVC5 which I can use to achieve this?
I would also like this to work on client side, can some body please help in enabling client side validation in MVC?
Many Thanks
Edit: Created the custom attribute, but client side validation not working.
public class ValidateToDateAttribute : ValidationAttribute, IClientValidatable
{
public string errorMessageKey { get; private set; }
public ValidateToDateAttribute(string errorMessageKey)
{
this.errorMessageKey = errorMessageKey;
}
protected override ValidationResult IsValid(object value, ValidationContext validationContext)
{
if (value != null)
{
var viewModel = (TransactionViewModel)validationContext.ObjectInstance;
if (viewModel.ToDate.CompareTo(viewModel.FromDate) < 0)
{
return new ValidationResult(new ResourceManager(typeof(ValidationErrorMessages)).GetString(errorMessageKey));
}
}
return ValidationResult.Success;
}
public IEnumerable<ModelClientValidationRule> GetClientValidationRules(ModelMetadata metadata, ControllerContext context)
{
var modelClientValidationRule = new ModelClientValidationRule
{
ValidationType = "validatetodate",
ErrorMessage = new ResourceManager(typeof(ValidationErrorMessages)).GetString(errorMessageKey)
};
yield return modelClientValidationRule;
}
}
}
Bundle.Config
bundles.Add(new ScriptBundle("~/bundles/jqueryvalidation").Include(
"~/Scripts/jquery.validate.unobtrusive.min.js",
"~/Scripts/jquery.unobtrusive-ajax.min.js"));
View Model
[Display(ResourceType = typeof(DisplayLabelText), Name = "FromDate")]
public DateTime FromDate { get; set; }
[Display(ResourceType = typeof(DisplayLabelText), Name = "ToDate")]
[ValidateToDate("ToDateMustBeGreater")]
public DateTime ToDate { get; set; }
in View:
<div class="col-sm-7 margin-top-10">
<div class="col-sm-12">
#Html.LabelFor(m => m.FromDate, new { #class = "col-sm-3 form-group control-label" })
<div class="col-sm-8">
#Html.TextBoxFor(m => m.FromDate, "{0:MMM dd yyyy}", new { #class = "datepicker", disabled = "disabled" })
</div>
</div>
<div class="col-sm-12">
#Html.LabelFor(m => m.ToDate, new { #class = "col-sm-3 form-group control-label" })
<div class="col-sm-8">
#Html.TextBoxFor(m => m.ToDate, "{0:MMM dd yyyy}", new { #class = "datepicker", disabled = "disabled" })
#Html.ValidationMessageFor(m => m.ToDate)
</div>
</div>
<button type="submit" class="apply-filter-button">Apply Filter</button>
</div>
I figured it out, I was missing the unobtrusive java script code:
please see the below, it might be helpful for some one.
public class ValidateToDateAttribute : ValidationAttribute, IClientValidatable
{
/// <summary>
/// Initializes a new instance of the <see cref="ValidateToDateAttribute"/> class.
/// </summary>
/// <param name="errorMessageKey">The error message key.</param>
public ValidateToDateAttribute(string errorMessageKey, string otherProperty)
{
this.ErrorMessageKey = errorMessageKey;
this.FromDate = otherProperty;
}
/// <summary>
/// Gets from date.
/// </summary>
/// <value>
/// From date.
/// </value>
public string FromDate { get; private set; }
/// <summary>
/// Gets the error message key.
/// </summary>
/// <value>
/// The error message key.
/// </value>
public string ErrorMessageKey { get; private set; }
/// <summary>
/// When implemented in a class, returns client validation rules for that class.
/// </summary>
/// <param name="metadata">The model metadata.</param>
/// <param name="context">The controller context.</param>
/// <returns>
/// The client validation rules for this validator.
/// </returns>
public IEnumerable<ModelClientValidationRule> GetClientValidationRules(ModelMetadata metadata, ControllerContext context)
{
var modelClientValidationRule = new ModelClientValidationRule
{
ValidationType = "validatetodate",
ErrorMessage = new ResourceManager(typeof(ValidationErrorMessages)).GetString(this.ErrorMessageKey),
};
modelClientValidationRule.ValidationParameters.Add("other", this.FromDate);
yield return modelClientValidationRule;
}
/// <summary>
/// Validates the specified value with respect to the current validation attribute.
/// </summary>
/// <param name="value">The value to validate.</param>
/// <param name="validationContext">The context information about the validation operation.</param>
/// <returns>
/// An instance of the <see cref="T:System.ComponentModel.DataAnnotations.ValidationResult" /> class.
/// </returns>
protected override ValidationResult IsValid(object value, ValidationContext validationContext)
{
if (value != null)
{
var viewModel = (TransactionViewModel)validationContext.ObjectInstance;
if (viewModel.ToDate.CompareTo(viewModel.FromDate) < 0)
{
return new ValidationResult(new ResourceManager(typeof(ValidationErrorMessages)).GetString(this.ErrorMessageKey));
}
}
return ValidationResult.Success;
}
}
JavaScript:
<script type="text/javascript">
jQuery.validator.addMethod('greaterThan', function (value, element, params) {
if (!/Invalid|NaN/.test(new Date(value))) {
//return new Date(value) > new Date($("input[name='FromDate']").val());
return Date.parse(value) > Date.parse($(params).val());
}
return isNaN(value) && isNaN($(fromDate)) || (parseFloat(value) > parseFloat($("input[name='FromDate']").val()));
}, '');
// and an unobtrusive adapter
jQuery.validator.unobtrusive.adapters.add('validatetodate', ["other"], function (options) {
options.rules['greaterThan'] = "#" + options.params.other;
options.messages['greaterThan'] = options.message;
});
I'm new to this repository pattern. I have the following methods in repository.
public abstract class Repository<T> : IRepository<T> where T : class
{
private PHOnlineEntities dataContext;
private readonly IDbSet<T> dbset;
protected Repository(IDatabaseFactory databaseFactory)
{
DatabaseFactory = databaseFactory;
dbset = DataContext.Set<T>();
}
protected IDatabaseFactory DatabaseFactory
{
get;
private set;
}
protected PHOnlineEntities DataContext
{
get { return dataContext ?? (dataContext = DatabaseFactory.Get()); }
}
public virtual int Add(T entity)
{
dbset.Add(entity);
dataContext.SaveChanges();
// return id here
}
public virtual void Update(T entity)
{
dbset.Attach(entity);
dataContext.Entry(entity).State = EntityState.Modified;
}
public virtual void Delete(T entity)
{
dbset.Remove(entity);
}
public virtual void Delete(Expression<Func<T, bool>> where)
{
IEnumerable<T> objects = dbset.Where<T>(where).AsEnumerable();
foreach (T obj in objects)
dbset.Remove(obj);
}
public virtual T GetById(long id)
{
return dbset.Find(id);
}
public virtual T GetById(string id)
{
return dbset.Find(id);
}
public virtual IEnumerable<T> GetAll()
{
return dbset.ToList();
}
public virtual IEnumerable<T> GetMany(Expression<Func<T, bool>> where)
{
return dbset.Where(where).ToList();
}
public T Get(Expression<Func<T, bool>> where)
{
return dbset.Where(where).FirstOrDefault<T>();
}
This is my CustomerRepository class
public interface ICustomerDetailRepository : IRepository<CustomerDetail>
{
}
/// <summary>
/// CustomerDetail repository
/// </summary>
public class CustomerDetailRepository : Repository<CustomerDetail>, ICustomerDetailRepository
{
/// <summary>
///
/// </summary>
private PHOnlineEntities _dataContext;
/// <summary>
///
/// </summary>
protected IDatabaseFactory DatabaseFactory
{
get;
private set;
}
/// <summary>
///
/// </summary>
/// <param name="databaseFactory"></param>
public CustomerDetailRepository(IDatabaseFactory databaseFactory)
: base(databaseFactory)
{
DatabaseFactory = databaseFactory;
}
/// <summary>
///
/// </summary>
protected PHOnlineEntities DataContext
{
get { return _dataContext ?? (_dataContext = DatabaseFactory.Get()); }
}
}
CustomerDetail class contains Model. It has all the Entity column along with ID column.
When i Add the entity to database, i want to return last inserted row id. The Id is identity column. Could anyone help me on this?
Check your CustomerDetail object after inserting, the ID will be populated already
public class CustomerDetail
{
public int Id{ get; set; }
public string Name{ get; set; }
public string Address{ get; set; }
}
var customerDetail = new CustomerDetail { Name = "Bubbles", Address = "1 way, city" }
customerDetailRepository.Add(customerDetail)
Console.WriteLine(customerDetail.Id); // This is the identity value
You have to create an interface like that:
public interface IEntity
{
public int Id { get; set;}
}
Make your entities implement that interface and change your repository class:
public abstract class Repository<T> : IRepository<T> where T : class, IEntity
{
(...)
public virtual int Add(T entity)
{
dbset.Add(entity);
dataContext.SaveChanges();
// return id here
return entity.Id;
}
}
I have a controller action that receives a complex object as a parameter, I need the OutputCache to vary by one of the properties of this complex object. Is this possible? How?
if you have a model like
public class person{
public string Name {get;set;}
public string location {get;set;}
}
and in the (strongly typed)view you have a form
#model Person
#Html.BeginForm(){
#Html.TextBoxFor(x=>x.Name)
#Html.TextBoxFor(x=>x.location)
}
and you submit the form to an ActionResult savePerson, with varying signature like
public ActionResult savePerson(Person p){
// p.Name
// p.location
}
or
public ActionResult savePerson(string Name, string location){
}
therefore i think if you annotate the ActionResult like
[OutputCache(Duration=3600, VaryByParam="Name")]
public ActionResult savePerson(Person p)
{
//
return View();
}
it will do for you, or if you have a complex model like
public class person{
public string Name {get;set;}
public Location loc {get;set;}
}
public class Location{
public string address
}
try
[OutputCache(Duration=3600, VaryByParam="Person.Location.address")]
public ActionResult savePerson(Person p)
{
//
return View();
}
I had the same requirement as above and came up with a slightly different approach
The class
/// <summary>
/// This class is used to encapsulate search filters for monitor graphs
/// </summary>
public class DatacarMonitorSearchCriteriaModel
{
public int? SynergyCode { get; set; }
[Required]
[DataType(DataType.Date)]
public DateTime StartDate { get; set; }
[Required]
[DataType(DataType.Date)]
public DateTime EndDate { get; set; }
/// <summary>
/// Filter to apply
/// </summary>
public IEnumerable<int> Countries { get; set; }
public DatacarMonitorSearchCriteriaModel()
{
Countries = new List<int>();
}
}
OutputCacheComplexAttribute
/// <summary>
/// <para>
/// An instance of this class mimic the behaviour of OutputCacheAttribute but for complex objects.
/// </para>
/// <para>
/// It allows to cache the output of any action that takes complex objects
/// </para>
/// </summary>
public class OutputCacheComplexAttribute : OutputCacheAttribute
{
private readonly Type[] _types;
private string _cachedKey;
/// <summary>
/// Initializes a new instance of the <see cref="OutputCacheComplexAttribute"/> class.
/// </summary>
/// <param name="types">Types that this attribute will lookup for in QueryString/Form data and store values in cache.</param>
/// <exception cref="System.ArgumentOutOfRangeException">type;type cannot be null</exception>
public OutputCacheComplexAttribute(params Type[] types)
{
if (types == null)
{
throw new ArgumentOutOfRangeException("type", "type cannot be null");
}
_types = types;
}
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
StringBuilder sbCachedKey = new StringBuilder();
if (filterContext.HttpContext.Request.Url != null)
{
string path = filterContext.HttpContext.Request.Url.PathAndQuery;
IDictionary<string, object> parameters = filterContext.ActionParameters;
//we need to compute a cache key which will be used to store the action output for later retrieval
//The cache key scheme is
// {url}:{key 1}:{value};[{key 2}:{value 2}[; ... {key n}:{value n}]];
// where :
// - url is the url of the action that will be executed
// - key n is the name of the n-th parameter
// - value n is the value of the n-th parameter as json string.
foreach (KeyValuePair<string, object> kv in parameters)
{
var kv1 = kv;
if (kv.Value != null && _types.AtLeastOnce(t => t.IsInstanceOfType(kv1.Value)))
{
sbCachedKey = sbCachedKey.AppendFormat("{0}:{1};",kv.Key,
JsonConvert.SerializeObject(kv.Value, Formatting.None, new JsonSerializerSettings()
{
NullValueHandling = NullValueHandling.Ignore,
ReferenceLoopHandling = ReferenceLoopHandling.Ignore
}));
}
}
_cachedKey = String.Format("{0}:{1}:{2}", GetType().Name, path, sbCachedKey.ToString());
}
if (!String.IsNullOrWhiteSpace(_cachedKey) && filterContext.HttpContext.Cache[_cachedKey] != null)
{
filterContext.Result = (ActionResult)filterContext.HttpContext.Cache[_cachedKey];
}
else
{
base.OnActionExecuting(filterContext);
}
}
public override void OnActionExecuted(ActionExecutedContext filterContext)
{
if (!String.IsNullOrWhiteSpace(_cachedKey))
{
filterContext.HttpContext.Cache.Add(_cachedKey, filterContext.Result, null,
DateTime.UtcNow.AddSeconds(Duration), Cache.NoSlidingExpiration,
CacheItemPriority.Default, null);
}
base.OnActionExecuted(filterContext);
}
}
Attribute usage
[OutputCacheComplex(typeof(DatacarMonitorSearchCriteriaModel), Duration = OutputCacheDurationInSeconds, Location = OutputCacheLocation.Server)]
public async Task<JsonNetResult<DatacarMonitorDetailModel>> ReadMonitorDetailsJson([DataSourceRequest] DataSourceRequest request, DatacarMonitorSearchCriteriaModel criteria)
{
//some really complicated code here
}
with this new attribute, you can specify which type[s] to use for caching and the cache key will be computed based on values of each its properties.
For object, just that work fine:
[OutputCache(VaryByParam = "*", Duration = 60)]
I'm having one of those days....
Here's my class:
/// <summary>
/// Represent a trimmed down version of the farms object for
/// presenting in lists.
/// </summary>
public class PagedFarm
{
/// <summary>
/// Gets or sets Name.
/// </summary>
public string Name { get; set; }
/// <summary>
/// Gets or sets Slug.
/// </summary>
public string Slug { get; set; }
/// <summary>
/// Gets or sets Rating.
/// </summary>
public int Rating { get; set; }
/// <summary>
/// Gets or sets City.
/// </summary>
public string City { get; set; }
/// <summary>
/// Gets or sets Crops.
/// </summary>
public List<string> Crops { get; set; }
}
Here's my meagre attempt to parse my parent Farm entity into the PagedFarm class.
int pageNumber = page ?? 1;
// Get a list of all the farms and hostels
var farms =
this.ReadOnlySession.Any<Farm>(x => x.Deleted == false).Select(
x =>
new PagedFarm
{
Name = x.Name,
Slug = x.Slug,
Rating = x.Rating,
City = x.City.Name,
// The line below doesn't work.
Crops = x.Crops.Select(c => new { c.Name })
.OrderBy(c => c.Name)
})
.ToPagedList(pageNumber, this.PageSize);
My error message:
Cannot implicitly convert type
System.Linq.IOrderedEnumerable<AnonymousType#1> to
System.Collections.Generic.List<string>. An explicit conversion
exists (are you missing a cast?)
Tried casting but no joy. What am I doing wrong?
I think you probably want:
Crops = x.Crops.Select(c => c.Name).OrderBy(name => name).ToList()
Try:
Crops = x.Crops.Select(crop => crop.Name) // Sequence of strings
.OrderBy(name => name) // Ordered sequence of strings
.ToList() // List of strings