Skip to content

Commit

Permalink
Merge branch 'neozhu:main' into main
Browse files Browse the repository at this point in the history
  • Loading branch information
kirkey authored Aug 18, 2024
2 parents 482bc2d + f3cd08b commit a3fdc45
Show file tree
Hide file tree
Showing 184 changed files with 10,112 additions and 7,100 deletions.
4 changes: 2 additions & 2 deletions CleanArchitecture.Blazor.nuspec
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
<package xmlns="http://schemas.microsoft.com/packaging/2012/06/nuspec.xsd">
<metadata>
<id>CleanArchitecture.Blazor.Solution.Template</id>
<version>1.0.0-preview.21</version>
<version>1.0.0-preview.26</version>
<title>Clean Architecture Blazor Solution Template</title>
<authors>hl.z</authors>
<description>Clean Architecture Blazor Solution Template for .NET 8.</description>
Expand All @@ -11,7 +11,7 @@
ASP.NET Core.
</summary>
<releaseNotes>
1.0.0-preview.21
1.0.0-preview.26
- 🌈 Integrate Stl.Fusion for real-time updates
- 🐛 fixed ocr process issue
- 🌈 Implement Google and Microsoft External Provider Authentication
Expand Down
11 changes: 10 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ Experience the application in action in Blazor Server mode by visiting
## Visual Insights

Dive into the application's aesthetics and functionality through screenshots and a video walkthrough.
[![Everything Is AWESOME](doc/page.png)](https://www.youtube.com/embed/GyZJl_dG-Pg "Everything Is AWESOME")
[![Everything Is AWESOME](doc/logo.png)](https://www.youtube.com/embed/GyZJl_dG-Pg "Everything Is AWESOME")

## Development Setup

Expand Down Expand Up @@ -209,6 +209,15 @@ Facebook.
- create a new project from Clean Architecture for Blazor Server Solution
<img width="769" alt="image" src="https://github.com/neozhu/CleanArchitectureWithBlazorServer/assets/1549611/ed7eb20f-aec2-4f69-95b7-d47c2eb20428">


## Tutorial: Removing an cutomer Object from a Project
[![Everything Is AWESOME](doc/remove.png)](https://www.youtube.com/watch?v=i3p-3I95YqM "Everything Is AWESOME")


## Tutorial: add an contact Entity in the project
[![Everything Is AWESOME](doc/create.png)](https://www.youtube.com/watch?v=X1b4hFLs4vo "Everything Is AWESOME")


## Why I chose Blazor Server

- I hate switching between C# and JavaScript at the same time in order to develop a project, which is why I opted for
Expand Down
Binary file added doc/create.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added doc/logo.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added doc/logo.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added doc/remove.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
8 changes: 4 additions & 4 deletions src/Application/Application.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
<LangVersion>default</LangVersion>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.DataProtection.EntityFrameworkCore" Version="8.0.6" />
<PackageReference Include="Microsoft.AspNetCore.DataProtection.EntityFrameworkCore" Version="8.0.8" />
<PackageReference Include="Ardalis.Specification" Version="8.0.0" />
<PackageReference Include="Ardalis.Specification.EntityFrameworkCore" Version="8.0.0" />
<PackageReference Include="ClosedXML" Version="0.104.0-preview2" />
Expand All @@ -20,9 +20,9 @@
<PackageReference Include="FluentValidation.DependencyInjectionExtensions" Version="11.9.2" />
<PackageReference Include="LazyCache" Version="2.4.0" />
<PackageReference Include="LazyCache.AspNetCore" Version="2.4.0" />
<PackageReference Include="Microsoft.AspNetCore.Components.Web" Version="8.0.6" />
<PackageReference Include="Microsoft.Extensions.Localization.Abstractions" Version="8.0.6" />
<PackageReference Include="System.Linq.Dynamic.Core" Version="1.4.0" />
<PackageReference Include="Microsoft.AspNetCore.Components.Web" Version="8.0.8" />
<PackageReference Include="Microsoft.Extensions.Localization.Abstractions" Version="8.0.8" />
<PackageReference Include="System.Linq.Dynamic.Core" Version="1.4.4" />
<PackageReference Include="Hangfire.Core" Version="1.8.14" />
</ItemGroup>
<ItemGroup>
Expand Down
2 changes: 1 addition & 1 deletion src/Application/Common/Interfaces/IApplicationDbContext.cs
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ public interface IApplicationDbContext
DbSet<KeyValue> KeyValues { get; set; }
DbSet<Product> Products { get; set; }
DbSet<Tenant> Tenants { get; set; }
DbSet<Customer> Customers { get; set; }
DbSet<Contact> Contacts { get; set; }
ChangeTracker ChangeTracker { get; }

DbSet<DataProtectionKey> DataProtectionKeys { get; set; }
Expand Down
4 changes: 0 additions & 4 deletions src/Application/Common/Interfaces/IApplicationSettings.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,7 @@ public interface IApplicationSettings
string AppFlavorSubscript { get; set; }
string ApplicationUrl { get; set; }
string AppName { get; set; }
bool BehindSSLProxy { get; set; }
string Company { get; set; }
string Copyright { get; set; }
string ProxyIP { get; set; }
bool Resilience { get; set; }
string Secret { get; set; }
string Version { get; set; }
}
6 changes: 4 additions & 2 deletions src/Application/Common/Models/UploadRequest.cs
Original file line number Diff line number Diff line change
@@ -1,19 +1,21 @@
// Licensed to the .NET Foundation under one or more agreements.
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

namespace CleanArchitecture.Blazor.Application.Common.Models;

public class UploadRequest
{
public UploadRequest(string fileName, UploadType uploadType, byte[] data)
public UploadRequest(string fileName, UploadType uploadType, byte[] data, bool overwrite=false)
{
FileName = fileName;
UploadType = uploadType;
Data = data;
Overwrite = overwrite;
}

public string FileName { get; set; }
public string? Extension { get; set; }
public UploadType UploadType { get; set; }
public bool Overwrite { get; set; }
public byte[] Data { get; set; }
}
35 changes: 35 additions & 0 deletions src/Application/Common/Security/UserProfileStateService.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace CleanArchitecture.Blazor.Application.Common.Security;

public class UserProfileStateService
{
private UserProfile _userProfile = new UserProfile() { Email="", UserId="", UserName="" };

public UserProfile UserProfile
{
get => _userProfile;
set
{
_userProfile = value;
NotifyStateChanged();
}
}

public event Action OnChange;

Check warning on line 23 in src/Application/Common/Security/UserProfileStateService.cs

View workflow job for this annotation

GitHub Actions / build

Non-nullable event 'OnChange' must contain a non-null value when exiting constructor. Consider adding the 'required' modifier or declaring the event as nullable.

private void NotifyStateChanged() => OnChange?.Invoke();

public void UpdateUserProfile(string? profilePictureDataUrl, string? fullName,string? phoneNumber)
{
_userProfile.ProfilePictureDataUrl = profilePictureDataUrl;
_userProfile.DisplayName = fullName;
_userProfile.PhoneNumber = phoneNumber;
NotifyStateChanged();
}
}

4 changes: 2 additions & 2 deletions src/Application/DependencyInjection.cs
Original file line number Diff line number Diff line change
Expand Up @@ -23,12 +23,12 @@ public static IServiceCollection AddApplication(this IServiceCollection services
config.AddOpenBehavior(typeof(UnhandledExceptionBehaviour<,>));
config.AddOpenBehavior(typeof(RequestExceptionProcessorBehavior<,>));
config.AddOpenBehavior(typeof(MemoryCacheBehaviour<,>));
config.AddOpenBehavior(typeof(AuthorizationBehaviour<,>));
//config.AddOpenBehavior(typeof(AuthorizationBehaviour<,>));
config.AddOpenBehavior(typeof(CacheInvalidationBehaviour<,>));
});

services.AddLazyCache();

services.AddScoped<UserProfileStateService>();
return services;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ namespace CleanArchitecture.Blazor.Application.Features.AuditTrails.Caching;
public static class AuditTrailsCacheKey
{
public const string GetAllCacheKey = "all-audittrails";
private static readonly TimeSpan RefreshInterval = TimeSpan.FromMilliseconds(30);
private static readonly TimeSpan RefreshInterval = TimeSpan.FromSeconds(30);
private static readonly object _tokenLock = new();
private static CancellationTokenSource _tokenSource = new(RefreshInterval);

Expand Down
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

namespace CleanArchitecture.Blazor.Application.Features.Customers.Caching;
namespace CleanArchitecture.Blazor.Application.Features.Contacts.Caching;
/// <summary>
/// Static class for managing cache keys and expiration for Customer-related data.
/// Static class for managing cache keys and expiration for Contact-related data.
/// </summary>
public static class CustomerCacheKey
public static class ContactCacheKey
{
// Defines the refresh interval for the cache expiration token
private static readonly TimeSpan RefreshInterval = TimeSpan.FromMinutes(30);
Expand All @@ -19,15 +19,15 @@ public static class CustomerCacheKey
public static MemoryCacheEntryOptions MemoryCacheEntryOptions =>
new MemoryCacheEntryOptions().AddExpirationToken(new CancellationChangeToken(GetOrCreateTokenSource().Token));

public const string GetAllCacheKey = "all-Customers";
public const string GetAllCacheKey = "all-Contacts";
public static string GetPaginationCacheKey(string parameters) {
return $"CustomerCacheKey:CustomersWithPaginationQuery,{parameters}";
return $"ContactCacheKey:ContactsWithPaginationQuery,{parameters}";
}
public static string GetByNameCacheKey(string parameters) {
return $"CustomerCacheKey:GetByNameCacheKey,{parameters}";
return $"ContactCacheKey:GetByNameCacheKey,{parameters}";
}
public static string GetByIdCacheKey(string parameters) {
return $"CustomerCacheKey:GetByIdCacheKey,{parameters}";
return $"ContactCacheKey:GetByIdCacheKey,{parameters}";
}


Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using CleanArchitecture.Blazor.Application.Features.Contacts.DTOs;
using CleanArchitecture.Blazor.Application.Features.Contacts.Caching;
namespace CleanArchitecture.Blazor.Application.Features.Contacts.Commands.AddEdit;

public class AddEditContactCommand: ICacheInvalidatorRequest<Result<int>>
{
[Description("Id")]
public int Id { get; set; }
[Description("Name")]
public string Name {get;set;} = String.Empty;
[Description("Description")]
public string? Description {get;set;}
[Description("Email")]
public string? Email {get;set;}
[Description("Phone Number")]
public string? PhoneNumber {get;set;}
[Description("Country")]
public string? Country {get;set;}


public string CacheKey => ContactCacheKey.GetAllCacheKey;
public CancellationTokenSource? SharedExpiryTokenSource => ContactCacheKey.GetOrCreateTokenSource();

private class Mapping : Profile
{
public Mapping()
{
CreateMap<ContactDto, AddEditContactCommand>(MemberList.None);
CreateMap<AddEditContactCommand,Contact>(MemberList.None);

}
}
}

public class AddEditContactCommandHandler : IRequestHandler<AddEditContactCommand, Result<int>>
{
private readonly IApplicationDbContext _context;
private readonly IMapper _mapper;
private readonly IStringLocalizer<AddEditContactCommandHandler> _localizer;
public AddEditContactCommandHandler(
IApplicationDbContext context,
IStringLocalizer<AddEditContactCommandHandler> localizer,
IMapper mapper
)
{
_context = context;
_localizer = localizer;
_mapper = mapper;
}
public async Task<Result<int>> Handle(AddEditContactCommand request, CancellationToken cancellationToken)
{
if (request.Id > 0)
{
var item = await _context.Contacts.FindAsync(new object[] { request.Id }, cancellationToken) ?? throw new NotFoundException($"Contact with id: [{request.Id}] not found.");
item = _mapper.Map(request, item);
// raise a update domain event
item.AddDomainEvent(new ContactUpdatedEvent(item));
await _context.SaveChangesAsync(cancellationToken);
return await Result<int>.SuccessAsync(item.Id);
}
else
{
var item = _mapper.Map<Contact>(request);
// raise a create domain event
item.AddDomainEvent(new ContactCreatedEvent(item));
_context.Contacts.Add(item);
await _context.SaveChangesAsync(cancellationToken);
return await Result<int>.SuccessAsync(item.Id);
}

}
}

Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

namespace CleanArchitecture.Blazor.Application.Features.Customers.Commands.AddEdit;
namespace CleanArchitecture.Blazor.Application.Features.Contacts.Commands.AddEdit;

public class AddEditCustomerCommandValidator : AbstractValidator<AddEditCustomerCommand>
public class AddEditContactCommandValidator : AbstractValidator<AddEditContactCommand>
{
public AddEditCustomerCommandValidator()
public AddEditContactCommandValidator()
{
RuleFor(v => v.Name)
.MaximumLength(256)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
using System.ComponentModel;
using CleanArchitecture.Blazor.Application.Features.Contacts.DTOs;
using CleanArchitecture.Blazor.Application.Features.Contacts.Caching;

namespace CleanArchitecture.Blazor.Application.Features.Contacts.Commands.Create;

public class CreateContactCommand: ICacheInvalidatorRequest<Result<int>>
{
[Description("Id")]
public int Id { get; set; }
[Description("Name")]
public string Name {get;set;} = String.Empty;
[Description("Description")]
public string? Description {get;set;}
[Description("Email")]
public string? Email {get;set;}
[Description("Phone Number")]
public string? PhoneNumber {get;set;}
[Description("Country")]
public string? Country {get;set;}

public string CacheKey => ContactCacheKey.GetAllCacheKey;
public CancellationTokenSource? SharedExpiryTokenSource => ContactCacheKey.GetOrCreateTokenSource();
private class Mapping : Profile
{
public Mapping()
{
CreateMap<ContactDto,CreateContactCommand>(MemberList.None);
CreateMap<CreateContactCommand,Contact>(MemberList.None);
}
}
}

public class CreateContactCommandHandler : IRequestHandler<CreateContactCommand, Result<int>>
{
private readonly IApplicationDbContext _context;
private readonly IMapper _mapper;
private readonly IStringLocalizer<CreateContactCommand> _localizer;
public CreateContactCommandHandler(
IApplicationDbContext context,
IStringLocalizer<CreateContactCommand> localizer,
IMapper mapper
)
{
_context = context;
_localizer = localizer;
_mapper = mapper;
}
public async Task<Result<int>> Handle(CreateContactCommand request, CancellationToken cancellationToken)
{
var item = _mapper.Map<Contact>(request);
// raise a create domain event
item.AddDomainEvent(new ContactCreatedEvent(item));
_context.Contacts.Add(item);
await _context.SaveChangesAsync(cancellationToken);
return await Result<int>.SuccessAsync(item.Id);
}
}

Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

namespace CleanArchitecture.Blazor.Application.Features.Customers.Commands.Create;
namespace CleanArchitecture.Blazor.Application.Features.Contacts.Commands.Create;

public class CreateCustomerCommandValidator : AbstractValidator<CreateCustomerCommand>
public class CreateContactCommandValidator : AbstractValidator<CreateContactCommand>
{
public CreateCustomerCommandValidator()
public CreateContactCommandValidator()
{

RuleFor(v => v.Name)
Expand Down
Loading

0 comments on commit a3fdc45

Please sign in to comment.